/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ambari.server.controller.internal; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.ClusterRequest; import org.apache.ambari.server.controller.ClusterResponse; import org.apache.ambari.server.controller.ConfigurationRequest; import org.apache.ambari.server.controller.RequestStatusResponse; import org.apache.ambari.server.controller.ServiceConfigVersionRequest; import org.apache.ambari.server.controller.ServiceConfigVersionResponse; import org.apache.ambari.server.controller.spi.NoSuchParentResourceException; import org.apache.ambari.server.controller.spi.NoSuchResourceException; import org.apache.ambari.server.controller.spi.Predicate; import org.apache.ambari.server.controller.spi.Request; import org.apache.ambari.server.controller.spi.RequestStatus; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException; import org.apache.ambari.server.controller.spi.SystemException; import org.apache.ambari.server.controller.spi.UnsupportedPropertyException; import org.apache.ambari.server.controller.utilities.PropertyHelper; import org.apache.ambari.server.security.authorization.AuthorizationException; import org.apache.ambari.server.security.authorization.AuthorizationHelper; import org.apache.ambari.server.security.authorization.ResourceType; import org.apache.ambari.server.security.authorization.RoleAuthorization; import org.apache.ambari.server.state.SecurityType; import org.apache.ambari.server.topology.InvalidTopologyException; import org.apache.ambari.server.topology.InvalidTopologyTemplateException; import org.apache.ambari.server.topology.SecurityConfiguration; import org.apache.ambari.server.topology.SecurityConfigurationFactory; import org.apache.ambari.server.topology.TopologyManager; import org.apache.ambari.server.topology.TopologyRequestFactory; import org.springframework.security.core.Authentication; import com.google.gson.Gson; /** * Resource provider for cluster resources. */ public class ClusterResourceProvider extends AbstractControllerResourceProvider { // ----- Property ID constants --------------------------------------------- // Clusters public static final String CLUSTER_ID_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "cluster_id"); public static final String CLUSTER_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "cluster_name"); public static final String CLUSTER_VERSION_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "version"); public static final String CLUSTER_PROVISIONING_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "provisioning_state"); public static final String CLUSTER_SECURITY_TYPE_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "security_type"); public static final String CLUSTER_DESIRED_CONFIGS_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "desired_configs"); public static final String CLUSTER_DESIRED_SERVICE_CONFIG_VERSIONS_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "desired_service_config_versions"); public static final String CLUSTER_TOTAL_HOSTS_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "total_hosts"); public static final String CLUSTER_HEALTH_REPORT_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "health_report"); public static final String CLUSTER_CREDENTIAL_STORE_PROPERTIES_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "credential_store_properties"); public static final String BLUEPRINT_PROPERTY_ID = PropertyHelper.getPropertyId(null, "blueprint"); public static final String SECURITY_PROPERTY_ID = PropertyHelper.getPropertyId(null, "security"); public static final String CREDENTIALS_PROPERTY_ID = PropertyHelper.getPropertyId(null, "credentials"); public static final String QUICKLINKS_PROFILE_PROPERTY_ID = PropertyHelper.getPropertyId(null, "quicklinks_profile"); public static final String SESSION_ATTRIBUTES_PROPERTY_ID = "session_attributes"; public static final String CLUSTER_REPO_VERSION = "Clusters/repository_version"; /** * The session attributes property prefix. */ private static final String SESSION_ATTRIBUTES_PROPERTY_PREFIX = SESSION_ATTRIBUTES_PROPERTY_ID + "/"; /** * Request info property ID. Allow internal getResources call to bypass permissions check. */ public static final String GET_IGNORE_PERMISSIONS_PROPERTY_ID = "get_resource/ignore_permissions"; /** * topology manager instance */ private static TopologyManager topologyManager; /** * factory for creating topology requests which are used to provision a cluster via a blueprint */ private static TopologyRequestFactory topologyRequestFactory; /** * Used to create SecurityConfiguration instances */ private static SecurityConfigurationFactory securityConfigurationFactory; /** * The cluster primary key properties. */ private static Set<String> pkPropertyIds = new HashSet<>(Arrays.asList(new String[]{CLUSTER_ID_PROPERTY_ID})); /** * The key property ids for a cluster resource. */ private static Map<Resource.Type, String> keyPropertyIds = new HashMap<>(); static { keyPropertyIds.put(Resource.Type.Cluster, CLUSTER_NAME_PROPERTY_ID); } /** * The property ids for a cluster resource. */ private static Set<String> propertyIds = new HashSet<>(); /** * Used to serialize to/from json. */ private static Gson jsonSerializer; static { propertyIds.add(CLUSTER_ID_PROPERTY_ID); propertyIds.add(CLUSTER_NAME_PROPERTY_ID); propertyIds.add(CLUSTER_VERSION_PROPERTY_ID); propertyIds.add(CLUSTER_PROVISIONING_STATE_PROPERTY_ID); propertyIds.add(CLUSTER_SECURITY_TYPE_PROPERTY_ID); propertyIds.add(CLUSTER_DESIRED_CONFIGS_PROPERTY_ID); propertyIds.add(CLUSTER_DESIRED_SERVICE_CONFIG_VERSIONS_PROPERTY_ID); propertyIds.add(CLUSTER_TOTAL_HOSTS_PROPERTY_ID); propertyIds.add(CLUSTER_HEALTH_REPORT_PROPERTY_ID); propertyIds.add(CLUSTER_CREDENTIAL_STORE_PROPERTIES_PROPERTY_ID); propertyIds.add(BLUEPRINT_PROPERTY_ID); propertyIds.add(SESSION_ATTRIBUTES_PROPERTY_ID); propertyIds.add(SECURITY_PROPERTY_ID); propertyIds.add(CREDENTIALS_PROPERTY_ID); propertyIds.add(CLUSTER_REPO_VERSION); propertyIds.add(QUICKLINKS_PROFILE_PROPERTY_ID); } // ----- Constructors ---------------------------------------------------- /** * Create a new resource provider for the given management controller. * * @param managementController the management controller */ ClusterResourceProvider(AmbariManagementController managementController) { super(propertyIds, keyPropertyIds, managementController); setRequiredCreateAuthorizations(EnumSet.of(RoleAuthorization.AMBARI_ADD_DELETE_CLUSTERS)); setRequiredDeleteAuthorizations(EnumSet.of(RoleAuthorization.AMBARI_ADD_DELETE_CLUSTERS)); setRequiredGetAuthorizations(RoleAuthorization.AUTHORIZATIONS_VIEW_CLUSTER); setRequiredUpdateAuthorizations(RoleAuthorization.AUTHORIZATIONS_UPDATE_CLUSTER); } // ----- ResourceProvider ------------------------------------------------ @Override protected Set<String> getPKPropertyIds() { return pkPropertyIds; } /** * {@inheritDoc} Overridden to support configuration. */ @Override public Set<String> checkPropertyIds(Set<String> propertyIds) { Set<String> baseUnsupported = super.checkPropertyIds(propertyIds); // extract to own method baseUnsupported.remove("blueprint"); baseUnsupported.remove("host_groups"); baseUnsupported.remove("default_password"); baseUnsupported.remove("configurations"); baseUnsupported.remove("credentials"); baseUnsupported.remove("config_recommendation_strategy"); baseUnsupported.remove("provision_action"); baseUnsupported.remove(ProvisionClusterRequest.REPO_VERSION_PROPERTY); return checkConfigPropertyIds(baseUnsupported, "Clusters"); } // ----- AbstractAuthorizedResourceProvider ------------------------------------------------ @Override protected boolean isAuthorizedToCreateResources(Authentication authentication, Request request) { return AuthorizationHelper.isAuthorized(authentication, ResourceType.AMBARI, null, getRequiredCreateAuthorizations()); } @Override protected boolean isAuthorizedToDeleteResources(Authentication authentication, Predicate predicate) throws SystemException { return AuthorizationHelper.isAuthorized(authentication, ResourceType.AMBARI, null, getRequiredDeleteAuthorizations()); } @Override protected RequestStatus createResourcesAuthorized(Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException { RequestStatusResponse createResponse = null; for (final Map<String, Object> properties : request.getProperties()) { if (isCreateFromBlueprint(properties)) { createResponse = processBlueprintCreate(properties, request.getRequestInfoProperties()); } else { createClusterResource(properties); } } notifyCreate(Resource.Type.Cluster, request); return getRequestStatus(createResponse); } @Override public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { final Set<ClusterRequest> requests = new HashSet<>(); if (predicate == null) { requests.add(getRequest(Collections.<String, Object>emptyMap())); } else { for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { requests.add(getRequest(propertyMap)); } } Set<String> requestedIds = getRequestPropertyIds(request, predicate); // Authorization checks are performed internally. If the user is not allowed to access a particular // cluster, it should not show up in the responses. Set<ClusterResponse> responses = getResources(new Command<Set<ClusterResponse>>() { @Override public Set<ClusterResponse> invoke() throws AmbariException, AuthorizationException { return getManagementController().getClusters(requests); } }); Set<Resource> resources = new HashSet<>(); if (LOG.isDebugEnabled()) { LOG.debug("Found clusters matching getClusters request" + ", clusterResponseCount=" + responses.size()); } // Allow internal call to bypass permissions check. for (ClusterResponse response : responses) { String clusterName = response.getClusterName(); Resource resource = new ResourceImpl(Resource.Type.Cluster); setResourceProperty(resource, CLUSTER_ID_PROPERTY_ID, response.getClusterId(), requestedIds); setResourceProperty(resource, CLUSTER_NAME_PROPERTY_ID, clusterName, requestedIds); setResourceProperty(resource, CLUSTER_PROVISIONING_STATE_PROPERTY_ID, response.getProvisioningState(), requestedIds); setResourceProperty(resource, CLUSTER_SECURITY_TYPE_PROPERTY_ID, response.getSecurityType(), requestedIds); setResourceProperty(resource, CLUSTER_DESIRED_CONFIGS_PROPERTY_ID, response.getDesiredConfigs(), requestedIds); setResourceProperty(resource, CLUSTER_DESIRED_SERVICE_CONFIG_VERSIONS_PROPERTY_ID, response.getDesiredServiceConfigVersions(), requestedIds); setResourceProperty(resource, CLUSTER_TOTAL_HOSTS_PROPERTY_ID, response.getTotalHosts(), requestedIds); setResourceProperty(resource, CLUSTER_HEALTH_REPORT_PROPERTY_ID, response.getClusterHealthReport(), requestedIds); setResourceProperty(resource, CLUSTER_CREDENTIAL_STORE_PROPERTIES_PROPERTY_ID, response.getCredentialStoreServiceProperties(), requestedIds); resource.setProperty(CLUSTER_VERSION_PROPERTY_ID, response.getDesiredStackVersion()); if (LOG.isDebugEnabled()) { LOG.debug("Adding ClusterResponse to resource" + ", clusterResponse=" + response.toString()); } resources.add(resource); } return resources; } @Override protected RequestStatus updateResourcesAuthorized(final Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { final Set<ClusterRequest> requests = new HashSet<>(); RequestStatusResponse response; for (Map<String, Object> requestPropertyMap : request.getProperties()) { Set<Map<String, Object>> propertyMaps = getPropertyMaps(requestPropertyMap, predicate); for (Map<String, Object> propertyMap : propertyMaps) { ClusterRequest clusterRequest = getRequest(propertyMap); requests.add(clusterRequest); } } response = modifyResources(new Command<RequestStatusResponse>() { @Override public RequestStatusResponse invoke() throws AmbariException, AuthorizationException { return getManagementController().updateClusters(requests, request.getRequestInfoProperties()); } }); notifyUpdate(Resource.Type.Cluster, request, predicate); Set<Resource> associatedResources = null; for (ClusterRequest clusterRequest : requests) { ClusterResponse updateResults = getManagementController().getClusterUpdateResults(clusterRequest); if (updateResults != null) { Map<String, Collection<ServiceConfigVersionResponse>> serviceConfigVersions = updateResults.getDesiredServiceConfigVersions(); if (serviceConfigVersions != null) { associatedResources = new HashSet<>(); for (Collection<ServiceConfigVersionResponse> scvCollection : serviceConfigVersions.values()) { for (ServiceConfigVersionResponse serviceConfigVersionResponse : scvCollection) { Resource resource = new ResourceImpl(Resource.Type.ServiceConfigVersion); resource.setProperty(ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_SERVICE_NAME_PROPERTY_ID, serviceConfigVersionResponse.getServiceName()); resource.setProperty(ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_PROPERTY_ID, serviceConfigVersionResponse.getVersion()); resource.setProperty(ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_NOTE_PROPERTY_ID, serviceConfigVersionResponse.getNote()); resource.setProperty(ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_GROUP_ID_PROPERTY_ID, serviceConfigVersionResponse.getGroupId()); resource.setProperty(ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_GROUP_NAME_PROPERTY_ID, serviceConfigVersionResponse.getGroupName()); if (serviceConfigVersionResponse.getConfigurations() != null) { resource.setProperty( ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_CONFIGURATIONS_PROPERTY_ID, serviceConfigVersionResponse.getConfigurations()); } associatedResources.add(resource); } } } } } return getRequestStatus(response, associatedResources); } @Override protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { final ClusterRequest clusterRequest = getRequest(propertyMap); modifyResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { getManagementController().deleteCluster(clusterRequest); return null; } }); } notifyDelete(Resource.Type.Cluster, predicate); return getRequestStatus(null); } // ----- ClusterResourceProvider ------------------------------------------- /** * Inject the blueprint data access object which is used to obtain blueprint entities. * @param manager topology manager * @param requestFactory request factory * @param instance */ //todo: proper static injection mechanism public static void init(TopologyManager manager, TopologyRequestFactory requestFactory, SecurityConfigurationFactory securityFactory, Gson instance) { topologyManager = manager; topologyRequestFactory = requestFactory; securityConfigurationFactory = securityFactory; jsonSerializer = instance; } // ----- utility methods --------------------------------------------------- /** * Get a cluster request object from a map of property values. * * @param properties the predicate * * @return the cluster request object */ private ClusterRequest getRequest(Map<String, Object> properties) { SecurityType securityType; String requestedSecurityType = (String) properties.get(CLUSTER_SECURITY_TYPE_PROPERTY_ID); if(requestedSecurityType == null) securityType = null; else { try { securityType = SecurityType.valueOf(requestedSecurityType.toUpperCase()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("Cannot set cluster security type to invalid value: %s", requestedSecurityType)); } } ClusterRequest cr = new ClusterRequest( (Long) properties.get(CLUSTER_ID_PROPERTY_ID), (String) properties.get(CLUSTER_NAME_PROPERTY_ID), (String) properties.get(CLUSTER_PROVISIONING_STATE_PROPERTY_ID), securityType, (String) properties.get(CLUSTER_VERSION_PROPERTY_ID), null, getSessionAttributes(properties)); if (properties.containsKey(CLUSTER_REPO_VERSION)) { cr.setRepositoryVersion(properties.get(CLUSTER_REPO_VERSION).toString()); } List<ConfigurationRequest> configRequests = getConfigurationRequests("Clusters", properties); ServiceConfigVersionRequest serviceConfigVersionRequest = getServiceConfigVersionRequest("Clusters", properties); if (!configRequests.isEmpty()) cr.setDesiredConfig(configRequests); if (serviceConfigVersionRequest != null) { cr.setServiceConfigVersionRequest(serviceConfigVersionRequest); } return cr; } /** * Get the map of session attributes from the given property map. * * @param properties the property map from the request * * @return the map of session attributes */ private Map<String, Object> getSessionAttributes(Map<String, Object> properties) { Map<String, Object> sessionAttributes = new HashMap<>(); for (Map.Entry<String, Object> entry : properties.entrySet()) { String property = entry.getKey(); if (property.startsWith(SESSION_ATTRIBUTES_PROPERTY_PREFIX)) { String attributeName = property.substring(SESSION_ATTRIBUTES_PROPERTY_PREFIX.length()); sessionAttributes.put(attributeName, entry.getValue()); } } return sessionAttributes; } /** * Helper method for creating rollback request */ protected ServiceConfigVersionRequest getServiceConfigVersionRequest(String parentCategory, Map<String, Object> properties) { ServiceConfigVersionRequest serviceConfigVersionRequest = null; for (Map.Entry<String, Object> entry : properties.entrySet()) { String absCategory = PropertyHelper.getPropertyCategory(entry.getKey()); String propName = PropertyHelper.getPropertyName(entry.getKey()); if (absCategory.startsWith(parentCategory + "/desired_service_config_version")) { serviceConfigVersionRequest = (serviceConfigVersionRequest ==null ) ? new ServiceConfigVersionRequest() : serviceConfigVersionRequest; if (propName.equals("service_name")) serviceConfigVersionRequest.setServiceName(entry.getValue().toString()); else if (propName.equals("service_config_version")) serviceConfigVersionRequest.setVersion(Long.valueOf(entry.getValue().toString())); else if (propName.equals("service_config_version_note")) { serviceConfigVersionRequest.setNote(entry.getValue().toString()); } } } return serviceConfigVersionRequest; } /** * Determine if the request is a create using a blueprint. * * @param properties request properties * * @return true if request is a create using a blueprint; false otherwise */ private boolean isCreateFromBlueprint(Map<String, Object> properties) { return properties.get("blueprint") != null; } /** * Process a create request specifying a blueprint. This includes creation of all resources, * setting of configuration and installing and starting of all services. The end result of this * call will be a running cluster based on the topology and configuration specified in the blueprint. * * @param properties request body properties * * @param requestInfoProperties raw request body * @return asynchronous response information * * @throws ResourceAlreadyExistsException if cluster already exists * @throws SystemException if an unexpected exception occurs * @throws UnsupportedPropertyException if an invalid property is specified in the request * @throws NoSuchParentResourceException if a necessary parent resource doesn't exist */ @SuppressWarnings("unchecked") private RequestStatusResponse processBlueprintCreate(Map<String, Object> properties, Map<String, String> requestInfoProperties) throws ResourceAlreadyExistsException, SystemException, UnsupportedPropertyException, NoSuchParentResourceException { LOG.info("Creating Cluster '" + properties.get(CLUSTER_NAME_PROPERTY_ID) + "' based on blueprint '" + String.valueOf(properties.get(BLUEPRINT_PROPERTY_ID)) + "'."); String rawRequestBody = requestInfoProperties.get(Request.REQUEST_INFO_BODY_PROPERTY); Map<String, Object> rawBodyMap = jsonSerializer.<Map<String, Object>>fromJson(rawRequestBody, Map.class); SecurityConfiguration securityConfiguration = securityConfigurationFactory.createSecurityConfigurationFromRequest(rawBodyMap, false); ProvisionClusterRequest createClusterRequest; try { createClusterRequest = topologyRequestFactory.createProvisionClusterRequest(properties, securityConfiguration); } catch (InvalidTopologyTemplateException e) { throw new IllegalArgumentException("Invalid Cluster Creation Template: " + e, e); } if (securityConfiguration != null && securityConfiguration.getType() == SecurityType.NONE && createClusterRequest.getBlueprint().getSecurity() != null && createClusterRequest.getBlueprint().getSecurity() .getType() == SecurityType.KERBEROS) { throw new IllegalArgumentException("Setting security to NONE is not allowed as security type in blueprint is set to KERBEROS!"); } try { return topologyManager.provisionCluster(createClusterRequest); } catch (InvalidTopologyException e) { throw new IllegalArgumentException("Topology validation failed: " + e, e); } catch (AmbariException e) { throw new SystemException("Unknown exception when asking TopologyManager to provision cluster", e); } catch (RuntimeException e) { throw new SystemException("An exception occurred during cluster provisioning: " + e.getMessage(), e); } } /** * Create the cluster resource. * * @param properties cluster resource request properties * * @throws ResourceAlreadyExistsException cluster resource already exists * @throws SystemException an unexpected exception occurred * @throws NoSuchParentResourceException shouldn't be thrown as a cluster doesn't have a parent resource */ private void createClusterResource(final Map<String, Object> properties) throws ResourceAlreadyExistsException, SystemException, NoSuchParentResourceException { createResources(new Command<Void>() { @Override public Void invoke() throws AmbariException, AuthorizationException { getManagementController().createCluster(getRequest(properties)); return null; } }); } }