| /** |
| * 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 com.google.inject.Inject; |
| import com.google.inject.assistedinject.Assisted; |
| import com.google.inject.assistedinject.AssistedInject; |
| import org.apache.ambari.server.AmbariException; |
| import org.apache.ambari.server.ClusterNotFoundException; |
| import org.apache.ambari.server.DuplicateResourceException; |
| import org.apache.ambari.server.ObjectNotFoundException; |
| import org.apache.ambari.server.ParentObjectNotFoundException; |
| import org.apache.ambari.server.RoleCommand; |
| import org.apache.ambari.server.ServiceNotFoundException; |
| import org.apache.ambari.server.api.services.AmbariMetaInfo; |
| import org.apache.ambari.server.controller.AmbariManagementController; |
| import org.apache.ambari.server.controller.KerberosHelper; |
| import org.apache.ambari.server.controller.MaintenanceStateHelper; |
| import org.apache.ambari.server.controller.RequestStatusResponse; |
| import org.apache.ambari.server.controller.ServiceRequest; |
| import org.apache.ambari.server.controller.ServiceResponse; |
| 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.controller.utilities.ServiceCalculatedStateFactory; |
| import org.apache.ambari.server.controller.utilities.state.ServiceCalculatedState; |
| import org.apache.ambari.server.metadata.RoleCommandOrder; |
| 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.serveraction.kerberos.KerberosAdminAuthenticationException; |
| import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException; |
| import org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException; |
| import org.apache.ambari.server.state.Cluster; |
| import org.apache.ambari.server.state.Clusters; |
| import org.apache.ambari.server.state.MaintenanceState; |
| import org.apache.ambari.server.state.Service; |
| import org.apache.ambari.server.state.ServiceComponent; |
| import org.apache.ambari.server.state.ServiceComponentHost; |
| import org.apache.ambari.server.state.ServiceFactory; |
| import org.apache.ambari.server.state.StackId; |
| import org.apache.ambari.server.state.State; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.commons.lang.Validate; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.EnumMap; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Resource provider for service resources. |
| */ |
| public class ServiceResourceProvider extends AbstractControllerResourceProvider { |
| |
| |
| // ----- Property ID constants --------------------------------------------- |
| |
| // Services |
| public static final String SERVICE_CLUSTER_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceInfo", "cluster_name"); |
| public static final String SERVICE_SERVICE_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceInfo", "service_name"); |
| public static final String SERVICE_SERVICE_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceInfo", "state"); |
| public static final String SERVICE_MAINTENANCE_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceInfo", "maintenance_state"); |
| |
| public static final String SERVICE_ATTRIBUTES_PROPERTY_ID = PropertyHelper.getPropertyId("Services", "attributes"); |
| |
| //Parameters from the predicate |
| private static final String QUERY_PARAMETERS_RUN_SMOKE_TEST_ID = |
| "params/run_smoke_test"; |
| |
| private static final String QUERY_PARAMETERS_RECONFIGURE_CLIENT = |
| "params/reconfigure_client"; |
| |
| private static final String QUERY_PARAMETERS_START_DEPENDENCIES = |
| "params/start_dependencies"; |
| |
| private static Set<String> pkPropertyIds = |
| new HashSet<String>(Arrays.asList(new String[]{ |
| SERVICE_CLUSTER_NAME_PROPERTY_ID, |
| SERVICE_SERVICE_NAME_PROPERTY_ID})); |
| |
| |
| private MaintenanceStateHelper maintenanceStateHelper; |
| |
| /** |
| * kerberos helper |
| */ |
| @Inject |
| private KerberosHelper kerberosHelper; |
| |
| // ----- Constructors ---------------------------------------------------- |
| |
| /** |
| * Create a new resource provider for the given management controller. |
| * |
| * @param propertyIds the property ids |
| * @param keyPropertyIds the key property ids |
| * @param managementController the management controller |
| */ |
| @AssistedInject |
| public ServiceResourceProvider(@Assisted Set<String> propertyIds, |
| @Assisted Map<Resource.Type, String> keyPropertyIds, |
| @Assisted AmbariManagementController managementController, |
| MaintenanceStateHelper maintenanceStateHelper) { |
| super(propertyIds, keyPropertyIds, managementController); |
| this.maintenanceStateHelper = maintenanceStateHelper; |
| |
| setRequiredCreateAuthorizations(EnumSet.of(RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)); |
| setRequiredUpdateAuthorizations(RoleAuthorization.AUTHORIZATIONS_UPDATE_SERVICE); |
| setRequiredGetAuthorizations(RoleAuthorization.AUTHORIZATIONS_VIEW_SERVICE); |
| setRequiredDeleteAuthorizations(EnumSet.of(RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)); |
| } |
| |
| // ----- ResourceProvider ------------------------------------------------ |
| |
| @Override |
| protected RequestStatus createResourcesAuthorized(Request request) |
| throws SystemException, |
| UnsupportedPropertyException, |
| ResourceAlreadyExistsException, |
| NoSuchParentResourceException { |
| |
| final Set<ServiceRequest> requests = new HashSet<>(); |
| for (Map<String, Object> propertyMap : request.getProperties()) { |
| requests.add(getRequest(propertyMap)); |
| } |
| createResources(new Command<Void>() { |
| @Override |
| public Void invoke() throws AmbariException, AuthorizationException { |
| createServices(requests); |
| return null; |
| } |
| }); |
| notifyCreate(Resource.Type.Service, request); |
| |
| return getRequestStatus(null); |
| } |
| |
| @Override |
| protected Set<Resource> getResourcesAuthorized(Request request, Predicate predicate) throws |
| SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { |
| |
| final Set<ServiceRequest> requests = new HashSet<ServiceRequest>(); |
| |
| for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { |
| requests.add(getRequest(propertyMap)); |
| } |
| |
| Set<ServiceResponse> responses = getResources(new Command<Set<ServiceResponse>>() { |
| @Override |
| public Set<ServiceResponse> invoke() throws AmbariException { |
| return getServices(requests); |
| } |
| }); |
| |
| Set<String> requestedIds = getRequestPropertyIds(request, predicate); |
| Set<Resource> resources = new HashSet<Resource>(); |
| |
| for (ServiceResponse response : responses) { |
| Resource resource = new ResourceImpl(Resource.Type.Service); |
| setResourceProperty(resource, SERVICE_CLUSTER_NAME_PROPERTY_ID, |
| response.getClusterName(), requestedIds); |
| setResourceProperty(resource, SERVICE_SERVICE_NAME_PROPERTY_ID, |
| response.getServiceName(), requestedIds); |
| setResourceProperty(resource, SERVICE_SERVICE_STATE_PROPERTY_ID, |
| calculateServiceState(response.getClusterName(), response.getServiceName()), |
| requestedIds); |
| setResourceProperty(resource, SERVICE_MAINTENANCE_STATE_PROPERTY_ID, |
| response.getMaintenanceState(), requestedIds); |
| |
| Map<String, Object> serviceSpecificProperties = getServiceSpecificProperties( |
| response.getClusterName(), response.getServiceName(), requestedIds); |
| |
| for (Map.Entry<String, Object> entry : serviceSpecificProperties.entrySet()) { |
| setResourceProperty(resource, entry.getKey(), entry.getValue(), requestedIds); |
| } |
| |
| resources.add(resource); |
| } |
| return resources; |
| } |
| |
| @Override |
| protected RequestStatus updateResourcesAuthorized(final Request request, Predicate predicate) |
| throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { |
| |
| RequestStageContainer requestStages = doUpdateResources(null, request, predicate); |
| |
| RequestStatusResponse response = null; |
| if (requestStages != null) { |
| try { |
| requestStages.persist(); |
| } catch (AmbariException e) { |
| throw new SystemException(e.getMessage(), e); |
| } |
| response = requestStages.getRequestStatusResponse(); |
| } |
| notifyUpdate(Resource.Type.Service, request, predicate); |
| |
| return getRequestStatus(response); |
| } |
| |
| @Override |
| protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate) |
| throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { |
| |
| final Set<ServiceRequest> requests = new HashSet<ServiceRequest>(); |
| for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { |
| requests.add(getRequest(propertyMap)); |
| } |
| RequestStatusResponse response = modifyResources(new Command<RequestStatusResponse>() { |
| @Override |
| public RequestStatusResponse invoke() throws AmbariException, AuthorizationException { |
| return deleteServices(requests); |
| } |
| }); |
| |
| notifyDelete(Resource.Type.Service, predicate); |
| return getRequestStatus(response); |
| } |
| |
| @Override |
| public Set<String> checkPropertyIds(Set<String> propertyIds) { |
| propertyIds = super.checkPropertyIds(propertyIds); |
| |
| if (propertyIds.isEmpty()) { |
| return propertyIds; |
| } |
| Set<String> unsupportedProperties = new HashSet<String>(); |
| |
| for (String propertyId : propertyIds) { |
| if (!propertyId.equals("config")) { |
| String propertyCategory = PropertyHelper.getPropertyCategory(propertyId); |
| if (propertyCategory == null || !propertyCategory.equals("config")) { |
| unsupportedProperties.add(propertyId); |
| } |
| } |
| } |
| return unsupportedProperties; |
| } |
| |
| |
| // ----- AbstractResourceProvider ---------------------------------------- |
| |
| @Override |
| protected Set<String> getPKPropertyIds() { |
| return pkPropertyIds; |
| } |
| |
| // ----- utility methods ------------------------------------------------- |
| |
| private RequestStageContainer doUpdateResources(final RequestStageContainer stages, final Request request, Predicate predicate) |
| throws UnsupportedPropertyException, SystemException, NoSuchResourceException, NoSuchParentResourceException { |
| |
| final Set<ServiceRequest> requests = new HashSet<ServiceRequest>(); |
| RequestStageContainer requestStages = null; |
| |
| Iterator<Map<String,Object>> iterator = request.getProperties().iterator(); |
| if (iterator.hasNext()) { |
| for (Map<String, Object> propertyMap : getPropertyMaps(iterator.next(), predicate)) { |
| requests.add(getRequest(propertyMap)); |
| } |
| |
| final boolean runSmokeTest = "true".equals(getQueryParameterValue( |
| QUERY_PARAMETERS_RUN_SMOKE_TEST_ID, predicate)); |
| |
| final boolean reconfigureClients = !"false".equals(getQueryParameterValue( |
| QUERY_PARAMETERS_RECONFIGURE_CLIENT, predicate)); |
| |
| final boolean startDependencies = "true".equals(getQueryParameterValue( |
| QUERY_PARAMETERS_START_DEPENDENCIES, predicate)); |
| |
| requestStages = modifyResources(new Command<RequestStageContainer>() { |
| @Override |
| public RequestStageContainer invoke() throws AmbariException, AuthorizationException { |
| return updateServices(stages, requests, request.getRequestInfoProperties(), |
| runSmokeTest, reconfigureClients, startDependencies); |
| } |
| }); |
| } |
| return requestStages; |
| } |
| |
| /** |
| * Get a service request object from a map of property values. |
| * |
| * @param properties the predicate |
| * |
| * @return the service request object |
| */ |
| private ServiceRequest getRequest(Map<String, Object> properties) { |
| ServiceRequest svcRequest = new ServiceRequest( |
| (String) properties.get(SERVICE_CLUSTER_NAME_PROPERTY_ID), |
| (String) properties.get(SERVICE_SERVICE_NAME_PROPERTY_ID), |
| (String) properties.get(SERVICE_SERVICE_STATE_PROPERTY_ID)); |
| |
| Object o = properties.get(SERVICE_MAINTENANCE_STATE_PROPERTY_ID); |
| if (null != o) { |
| svcRequest.setMaintenanceState(o.toString()); |
| } |
| |
| return svcRequest; |
| } |
| |
| // Create services from the given request. |
| public synchronized void createServices(Set<ServiceRequest> requests) |
| throws AmbariException, AuthorizationException { |
| |
| if (requests.isEmpty()) { |
| LOG.warn("Received an empty requests set"); |
| return; |
| } |
| Clusters clusters = getManagementController().getClusters(); |
| // do all validation checks |
| validateCreateRequests(requests, clusters); |
| |
| ServiceFactory serviceFactory = getManagementController().getServiceFactory(); |
| for (ServiceRequest request : requests) { |
| Cluster cluster = clusters.getCluster(request.getClusterName()); |
| |
| State state = State.INIT; |
| |
| // Already checked that service does not exist |
| Service s = serviceFactory.createNew(cluster, request.getServiceName()); |
| |
| s.setDesiredState(state); |
| s.setDesiredStackVersion(cluster.getDesiredStackVersion()); |
| s.persist(); |
| // Initialize service widgets |
| getManagementController().initializeWidgetsAndLayouts(cluster, s); |
| } |
| } |
| |
| // Get services from the given set of requests. |
| protected Set<ServiceResponse> getServices(Set<ServiceRequest> requests) |
| throws AmbariException { |
| Set<ServiceResponse> response = new HashSet<ServiceResponse>(); |
| for (ServiceRequest request : requests) { |
| try { |
| response.addAll(getServices(request)); |
| } catch (ServiceNotFoundException e) { |
| if (requests.size() == 1) { |
| // only throw exception if 1 request. |
| // there will be > 1 request in case of OR predicate |
| throw e; |
| } |
| } |
| } |
| return response; |
| } |
| |
| // Get services from the given request. |
| private Set<ServiceResponse> getServices(ServiceRequest request) |
| throws AmbariException { |
| if (request.getClusterName() == null |
| || request.getClusterName().isEmpty()) { |
| throw new AmbariException("Invalid arguments, cluster name" |
| + " cannot be null"); |
| } |
| Clusters clusters = getManagementController().getClusters(); |
| String clusterName = request.getClusterName(); |
| |
| final Cluster cluster; |
| try { |
| cluster = clusters.getCluster(clusterName); |
| } catch (ObjectNotFoundException e) { |
| throw new ParentObjectNotFoundException("Parent Cluster resource doesn't exist", e); |
| } |
| |
| Set<ServiceResponse> response = new HashSet<ServiceResponse>(); |
| if (request.getServiceName() != null) { |
| Service s = cluster.getService(request.getServiceName()); |
| response.add(s.convertToResponse()); |
| return response; |
| } |
| |
| // TODO support search on predicates? |
| |
| boolean checkDesiredState = false; |
| State desiredStateToCheck = null; |
| if (request.getDesiredState() != null |
| && !request.getDesiredState().isEmpty()) { |
| desiredStateToCheck = State.valueOf(request.getDesiredState()); |
| if (!desiredStateToCheck.isValidDesiredState()) { |
| throw new IllegalArgumentException("Invalid arguments, invalid desired" |
| + " state, desiredState=" + desiredStateToCheck); |
| } |
| checkDesiredState = true; |
| } |
| |
| for (Service s : cluster.getServices().values()) { |
| if (checkDesiredState |
| && (desiredStateToCheck != s.getDesiredState())) { |
| // skip non matching state |
| continue; |
| } |
| response.add(s.convertToResponse()); |
| } |
| return response; |
| } |
| |
| // Update services based on the given requests. |
| protected synchronized RequestStageContainer updateServices(RequestStageContainer requestStages, Set<ServiceRequest> requests, |
| Map<String, String> requestProperties, boolean runSmokeTest, |
| boolean reconfigureClients, boolean startDependencies) throws AmbariException, AuthorizationException { |
| |
| AmbariManagementController controller = getManagementController(); |
| |
| if (requests.isEmpty()) { |
| LOG.warn("Received an empty requests set"); |
| return null; |
| } |
| |
| Map<State, List<Service>> changedServices |
| = new EnumMap<State, List<Service>>(State.class); |
| Map<State, List<ServiceComponent>> changedComps = |
| new EnumMap<State, List<ServiceComponent>>(State.class); |
| Map<String, Map<State, List<ServiceComponentHost>>> changedScHosts = |
| new HashMap<String, Map<State, List<ServiceComponentHost>>>(); |
| Collection<ServiceComponentHost> ignoredScHosts = |
| new ArrayList<ServiceComponentHost>(); |
| |
| Set<String> clusterNames = new HashSet<String>(); |
| Map<String, Set<String>> serviceNames = new HashMap<String, Set<String>>(); |
| Set<State> seenNewStates = new HashSet<State>(); |
| |
| // Determine operation level |
| Resource.Type reqOpLvl; |
| if (requestProperties.containsKey(RequestOperationLevel.OPERATION_LEVEL_ID)) { |
| RequestOperationLevel operationLevel = new RequestOperationLevel(requestProperties); |
| reqOpLvl = operationLevel.getLevel(); |
| } else { |
| String message = "Can not determine request operation level. " + |
| "Operation level property should " + |
| "be specified for this request."; |
| LOG.warn(message); |
| reqOpLvl = Resource.Type.Cluster; |
| } |
| |
| Clusters clusters = controller.getClusters(); |
| |
| // We don't expect batch requests for different clusters, that's why |
| // nothing bad should happen if value is overwritten few times |
| String maintenanceCluster = null; |
| |
| for (ServiceRequest request : requests) { |
| if (request.getClusterName() == null |
| || request.getClusterName().isEmpty() |
| || request.getServiceName() == null |
| || request.getServiceName().isEmpty()) { |
| throw new IllegalArgumentException("Invalid arguments, cluster name" |
| + " and service name should be provided to update services"); |
| } |
| |
| LOG.info("Received a updateService request" |
| + ", clusterName=" + request.getClusterName() |
| + ", serviceName=" + request.getServiceName() |
| + ", request=" + request.toString()); |
| |
| clusterNames.add(request.getClusterName()); |
| |
| if (clusterNames.size() > 1) { |
| throw new IllegalArgumentException("Updates to multiple clusters is not" |
| + " supported"); |
| } |
| |
| if (!serviceNames.containsKey(request.getClusterName())) { |
| serviceNames.put(request.getClusterName(), new HashSet<String>()); |
| } |
| if (serviceNames.get(request.getClusterName()) |
| .contains(request.getServiceName())) { |
| // TODO throw single exception |
| throw new IllegalArgumentException("Invalid request contains duplicate" |
| + " service names"); |
| } |
| serviceNames.get(request.getClusterName()).add(request.getServiceName()); |
| |
| Cluster cluster = clusters.getCluster(request.getClusterName()); |
| Service s = cluster.getService(request.getServiceName()); |
| State oldState = s.getDesiredState(); |
| State newState = null; |
| if (request.getDesiredState() != null) { |
| newState = State.valueOf(request.getDesiredState()); |
| if (!newState.isValidDesiredState()) { |
| throw new IllegalArgumentException("Invalid arguments, invalid" |
| + " desired state, desiredState=" + newState); |
| } |
| } |
| |
| // Setting Maintenance state for service |
| if (null != request.getMaintenanceState()) { |
| if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, cluster.getResourceId(), RoleAuthorization.SERVICE_TOGGLE_MAINTENANCE)) { |
| throw new AuthorizationException("The authenticated user is not authorized to toggle the maintainence state of services"); |
| } |
| |
| MaintenanceState newMaint = MaintenanceState.valueOf(request.getMaintenanceState()); |
| if (newMaint != s.getMaintenanceState()) { |
| if (newMaint.equals(MaintenanceState.IMPLIED_FROM_HOST) |
| || newMaint.equals(MaintenanceState.IMPLIED_FROM_SERVICE)) { |
| throw new IllegalArgumentException("Invalid arguments, can only set " + |
| "maintenance state to one of " + EnumSet.of(MaintenanceState.OFF, MaintenanceState.ON)); |
| } else { |
| s.setMaintenanceState(newMaint); |
| maintenanceCluster = cluster.getClusterName(); |
| } |
| } |
| } |
| |
| if (newState == null) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Nothing to do for new updateService request" |
| + ", clusterName=" + request.getClusterName() |
| + ", serviceName=" + request.getServiceName() |
| + ", newDesiredState=null"); |
| } |
| continue; |
| } |
| |
| if (! maintenanceStateHelper.isOperationAllowed(reqOpLvl, s)) { |
| LOG.info("Operations cannot be applied to service " + s.getName() + |
| " in the maintenance state of " + s.getMaintenanceState()); |
| continue; |
| } |
| |
| seenNewStates.add(newState); |
| |
| if (newState != oldState) { |
| // The if user is trying to start or stop the service, ensure authorization |
| if (((newState == State.INSTALLED) || (newState == State.STARTED)) && |
| !AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, cluster.getResourceId(), RoleAuthorization.SERVICE_START_STOP)) { |
| throw new AuthorizationException("The authenticated user is not authorized to start or stop services"); |
| } |
| |
| if (!State.isValidDesiredStateTransition(oldState, newState)) { |
| throw new AmbariException("Invalid transition for" |
| + " service" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", clusterId=" + cluster.getClusterId() |
| + ", serviceName=" + s.getName() |
| + ", currentDesiredState=" + oldState |
| + ", newDesiredState=" + newState); |
| |
| } |
| if (!changedServices.containsKey(newState)) { |
| changedServices.put(newState, new ArrayList<Service>()); |
| } |
| changedServices.get(newState).add(s); |
| } |
| |
| // TODO should we check whether all servicecomponents and |
| // servicecomponenthosts are in the required desired state? |
| |
| updateServiceComponents(requestStages, changedComps, changedScHosts, |
| ignoredScHosts, reqOpLvl, s, newState); |
| } |
| |
| if (startDependencies && changedServices.containsKey(State.STARTED)) { |
| HashSet<Service> depServices = new HashSet<Service>(); |
| for (Service service : changedServices.get(State.STARTED)) { |
| RoleCommandOrder rco = controller.getRoleCommandOrder(service.getCluster()); |
| Set<Service> dependencies = rco.getTransitiveServices(service, RoleCommand.START); |
| for (Service dependency: dependencies) { |
| if (!changedServices.get(State.STARTED).contains(dependency)){ |
| depServices.add(dependency); |
| } |
| } |
| } |
| for (Service service : depServices) { |
| updateServiceComponents(requestStages, changedComps, changedScHosts, |
| ignoredScHosts, reqOpLvl, service, State.STARTED); |
| changedServices.get(State.STARTED).add(service); |
| } |
| |
| } |
| |
| if (seenNewStates.size() > 1) { |
| // TODO should we handle this scenario |
| throw new IllegalArgumentException("Cannot handle different desired state" |
| + " changes for a set of services at the same time"); |
| } |
| |
| Cluster cluster = clusters.getCluster(clusterNames.iterator().next()); |
| |
| return controller.addStages(requestStages, cluster, requestProperties, |
| null, changedServices, changedComps, changedScHosts, |
| ignoredScHosts, runSmokeTest, reconfigureClients); |
| } |
| |
| private void updateServiceComponents(RequestStageContainer requestStages, |
| Map<State, List<ServiceComponent>> changedComps, |
| Map<String, Map<State, List<ServiceComponentHost>>> changedScHosts, |
| Collection<ServiceComponentHost> ignoredScHosts, |
| Resource.Type reqOpLvl, |
| Service service, State newState) |
| throws AmbariException { |
| |
| Cluster cluster = service.getCluster(); |
| AmbariManagementController controller = getManagementController(); |
| AmbariMetaInfo ambariMetaInfo = controller.getAmbariMetaInfo(); |
| |
| for (ServiceComponent sc : service.getServiceComponents().values()) { |
| State oldScState = sc.getDesiredState(); |
| if (newState != oldScState) { |
| if (sc.isClientComponent() && |
| !newState.isValidClientComponentState()) { |
| continue; |
| } |
| if (!State.isValidDesiredStateTransition(oldScState, newState)) { |
| throw new AmbariException("Invalid transition for" |
| + " servicecomponent" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", clusterId=" + cluster.getClusterId() |
| + ", serviceName=" + sc.getServiceName() |
| + ", componentName=" + sc.getName() |
| + ", currentDesiredState=" + oldScState |
| + ", newDesiredState=" + newState); |
| } |
| if (!changedComps.containsKey(newState)) { |
| changedComps.put(newState, new ArrayList<ServiceComponent>()); |
| } |
| changedComps.get(newState).add(sc); |
| } |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Handling update to ServiceComponent" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", serviceName=" + service.getName() |
| + ", componentName=" + sc.getName() |
| + ", currentDesiredState=" + oldScState |
| + ", newDesiredState=" + newState); |
| } |
| |
| for (ServiceComponentHost sch : sc.getServiceComponentHosts().values()) { |
| State oldSchState = sch.getState(); |
| if (oldSchState == State.DISABLED || oldSchState == State.UNKNOWN) { |
| //Ignore host components updates in this state |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Ignoring ServiceComponentHost" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", serviceName=" + service.getName() |
| + ", componentName=" + sc.getName() |
| + ", hostname=" + sch.getHostName() |
| + ", currentState=" + oldSchState |
| + ", newDesiredState=" + newState); |
| } |
| continue; |
| } |
| // |
| if (newState == oldSchState) { |
| ignoredScHosts.add(sch); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Ignoring ServiceComponentHost" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", serviceName=" + service.getName() |
| + ", componentName=" + sc.getName() |
| + ", hostname=" + sch.getHostName() |
| + ", currentState=" + oldSchState |
| + ", newDesiredState=" + newState); |
| } |
| continue; |
| } |
| |
| if (! maintenanceStateHelper.isOperationAllowed(reqOpLvl, sch)) { |
| ignoredScHosts.add(sch); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Ignoring ServiceComponentHost" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", serviceName=" + service.getName() |
| + ", componentName=" + sc.getName() |
| + ", hostname=" + sch.getHostName()); |
| } |
| continue; |
| } |
| |
| if (sc.isClientComponent() && |
| !newState.isValidClientComponentState()) { |
| continue; |
| } |
| /** |
| * This is hack for now wherein we don't fail if the |
| * sch is in INSTALL_FAILED |
| */ |
| if (! isValidStateTransition(requestStages, oldSchState, newState, sch)) { |
| String error = "Invalid transition for" |
| + " servicecomponenthost" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", clusterId=" + cluster.getClusterId() |
| + ", serviceName=" + sch.getServiceName() |
| + ", componentName=" + sch.getServiceComponentName() |
| + ", hostname=" + sch.getHostName() |
| + ", currentState=" + oldSchState |
| + ", newDesiredState=" + newState; |
| StackId sid = cluster.getDesiredStackVersion(); |
| |
| if ( ambariMetaInfo.getComponent( |
| sid.getStackName(), sid.getStackVersion(), sc.getServiceName(), |
| sch.getServiceComponentName()).isMaster()) { |
| throw new AmbariException(error); |
| } else { |
| LOG.warn("Ignoring: " + error); |
| continue; |
| } |
| } |
| if (!changedScHosts.containsKey(sc.getName())) { |
| changedScHosts.put(sc.getName(), |
| new EnumMap<State, List<ServiceComponentHost>>(State.class)); |
| } |
| if (!changedScHosts.get(sc.getName()).containsKey(newState)) { |
| changedScHosts.get(sc.getName()).put(newState, |
| new ArrayList<ServiceComponentHost>()); |
| } |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Handling update to ServiceComponentHost" |
| + ", clusterName=" + cluster.getClusterName() |
| + ", serviceName=" + service.getName() |
| + ", componentName=" + sc.getName() |
| + ", hostname=" + sch.getHostName() |
| + ", currentState=" + oldSchState |
| + ", newDesiredState=" + newState); |
| } |
| changedScHosts.get(sc.getName()).get(newState).add(sch); |
| } |
| } |
| } |
| |
| // Delete services based on the given set of requests |
| protected RequestStatusResponse deleteServices(Set<ServiceRequest> request) |
| throws AmbariException, AuthorizationException { |
| |
| Clusters clusters = getManagementController().getClusters(); |
| |
| Set<Service> removable = new HashSet<Service>(); |
| |
| for (ServiceRequest serviceRequest : request) { |
| if (StringUtils.isEmpty(serviceRequest.getClusterName()) || StringUtils.isEmpty(serviceRequest.getServiceName())) { |
| // FIXME throw correct error |
| throw new AmbariException("invalid arguments"); |
| } else { |
| |
| if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceId(serviceRequest.getClusterName()), RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) { |
| throw new AuthorizationException("The user is not authorized to delete services"); |
| } |
| |
| Service service = clusters.getCluster( |
| serviceRequest.getClusterName()).getService( |
| serviceRequest.getServiceName()); |
| |
| // |
| // Run through the list of service component hosts. If all host components are in removable state, |
| // the service can be deleted, irrespective of it's state. |
| // |
| boolean isServiceRemovable = true; |
| |
| for (ServiceComponent sc : service.getServiceComponents().values()) { |
| Map<String, ServiceComponentHost> schHostMap = sc.getServiceComponentHosts(); |
| |
| for (Map.Entry<String, ServiceComponentHost> entry : schHostMap.entrySet()) { |
| ServiceComponentHost sch = entry.getValue(); |
| if (!sch.canBeRemoved()) { |
| String msg = "Cannot remove " + serviceRequest.getClusterName() + "/" + serviceRequest.getServiceName() + |
| ". " + sch.getServiceComponentName() + "on " + sch.getHost() + " is in " + |
| String.valueOf(sch.getDesiredState()) + " state."; |
| LOG.error(msg); |
| isServiceRemovable = false; |
| } |
| } |
| } |
| |
| if (!isServiceRemovable) { |
| throw new AmbariException ("Cannot remove " + |
| serviceRequest.getClusterName() + "/" + serviceRequest.getServiceName() + |
| ". " + "One or more host components are in a non-removable state."); |
| } |
| |
| removable.add(service); |
| } |
| } |
| |
| for (Service service : removable) { |
| service.getCluster().deleteService(service.getName()); |
| } |
| |
| return null; |
| } |
| |
| // calculate the service state, accounting for the state of the host components |
| private String calculateServiceState(String clusterName, String serviceName) { |
| ServiceCalculatedState serviceCalculatedState = ServiceCalculatedStateFactory.getServiceStateProvider(serviceName); |
| return serviceCalculatedState.getState(clusterName, serviceName).toString(); |
| } |
| |
| /** |
| * Determine whether a service state change is valid. |
| * Looks at projected state from the current stages associated with the request. |
| * |
| * |
| * @param stages request stages |
| * @param startState service start state |
| * @param desiredState service desired state |
| * @param host host where state change is occurring |
| * |
| * @return whether the state transition is valid |
| */ |
| private boolean isValidStateTransition(RequestStageContainer stages, State startState, |
| State desiredState, ServiceComponentHost host) { |
| |
| if (stages != null) { |
| State projectedState = stages.getProjectedState(host.getHostName(), host.getServiceComponentName()); |
| startState = projectedState == null ? startState : projectedState; |
| } |
| |
| return State.isValidStateTransition(startState, desiredState); |
| } |
| |
| /** |
| * Get any service specific properties for the request. |
| * |
| * @param clusterName cluster name |
| * @param serviceName service name |
| * @param requestedIds relevant request property ids |
| */ |
| private Map<String, Object> getServiceSpecificProperties(String clusterName, String serviceName, Set<String> requestedIds) { |
| Map<String, Object> serviceSpecificProperties = new HashMap<String, Object>(); |
| if (serviceName.equals("KERBEROS")) { |
| // Only include details on whether the KDC administrator credentials are set and correct if |
| // implicitly (Service/attributes) or explicitly (Service/attributes/kdc_...) queried |
| if (requestedIds.contains(SERVICE_ATTRIBUTES_PROPERTY_ID) || |
| isPropertyCategoryRequested(SERVICE_ATTRIBUTES_PROPERTY_ID, requestedIds) || |
| isPropertyEntryRequested(SERVICE_ATTRIBUTES_PROPERTY_ID, requestedIds)) { |
| Map<String, String> kerberosAttributes = new HashMap<String, String>(); |
| String kdcValidationResult = "OK"; |
| String failureDetails = ""; |
| try { |
| kerberosHelper.validateKDCCredentials( |
| getManagementController().getClusters().getCluster(clusterName)); |
| |
| } catch (KerberosInvalidConfigurationException e) { |
| kdcValidationResult = "INVALID_CONFIGURATION"; |
| failureDetails = e.getMessage(); |
| } catch (KerberosAdminAuthenticationException e) { |
| kdcValidationResult = "INVALID_CREDENTIALS"; |
| failureDetails = e.getMessage(); |
| } catch (KerberosMissingAdminCredentialsException e) { |
| kdcValidationResult = "MISSING_CREDENTIALS"; |
| failureDetails = e.getMessage(); |
| } catch (AmbariException e) { |
| kdcValidationResult = "VALIDATION_ERROR"; |
| failureDetails = e.getMessage(); |
| } |
| |
| kerberosAttributes.put("kdc_validation_result", kdcValidationResult); |
| kerberosAttributes.put("kdc_validation_failure_details", failureDetails); |
| serviceSpecificProperties.put(SERVICE_ATTRIBUTES_PROPERTY_ID, kerberosAttributes); |
| } |
| } |
| |
| return serviceSpecificProperties; |
| } |
| |
| private void validateCreateRequests(Set<ServiceRequest> requests, Clusters clusters) |
| throws AuthorizationException, AmbariException { |
| |
| AmbariMetaInfo ambariMetaInfo = getManagementController().getAmbariMetaInfo(); |
| Map<String, Set<String>> serviceNames = new HashMap<>(); |
| Set<String> duplicates = new HashSet<>(); |
| for (ServiceRequest request : requests) { |
| final String clusterName = request.getClusterName(); |
| final String serviceName = request.getServiceName(); |
| Validate.notEmpty(clusterName, "Cluster name should be provided when creating a service"); |
| Validate.notEmpty(serviceName, "Service name should be provided when creating a service"); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Received a createService request" |
| + ", clusterName=" + clusterName + ", serviceName=" + serviceName + ", request=" + request); |
| } |
| |
| if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceId(clusterName), RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) { |
| throw new AuthorizationException("The user is not authorized to create services"); |
| } |
| |
| if (!serviceNames.containsKey(clusterName)) { |
| serviceNames.put(clusterName, new HashSet<String>()); |
| } |
| |
| if (serviceNames.get(clusterName).contains(serviceName)) { |
| // throw error later for dup |
| duplicates.add(serviceName); |
| continue; |
| } |
| serviceNames.get(clusterName).add(serviceName); |
| |
| if (StringUtils.isNotEmpty(request.getDesiredState())) { |
| State state = State.valueOf(request.getDesiredState()); |
| if (!state.isValidDesiredState() || state != State.INIT) { |
| throw new IllegalArgumentException("Invalid desired state" |
| + " only INIT state allowed during creation" |
| + ", providedDesiredState=" + request.getDesiredState()); |
| } |
| } |
| |
| Cluster cluster; |
| try { |
| cluster = clusters.getCluster(clusterName); |
| } catch (ClusterNotFoundException e) { |
| throw new ParentObjectNotFoundException("Attempted to add a service to a cluster which doesn't exist", e); |
| } |
| try { |
| Service s = cluster.getService(serviceName); |
| if (s != null) { |
| // throw error later for dup |
| duplicates.add(serviceName); |
| continue; |
| } |
| } catch (ServiceNotFoundException e) { |
| // Expected |
| } |
| |
| StackId stackId = cluster.getDesiredStackVersion(); |
| if (!ambariMetaInfo.isValidService(stackId.getStackName(), |
| stackId.getStackVersion(), request.getServiceName())) { |
| throw new IllegalArgumentException("Unsupported or invalid service in stack, clusterName=" + clusterName |
| + ", serviceName=" + serviceName + ", stackInfo=" + stackId.getStackId()); |
| } |
| } |
| // ensure only a single cluster update |
| if (serviceNames.size() != 1) { |
| throw new IllegalArgumentException("Invalid arguments, updates allowed" |
| + "on only one cluster at a time"); |
| } |
| |
| // Validate dups |
| if (!duplicates.isEmpty()) { |
| String clusterName = requests.iterator().next().getClusterName(); |
| String msg = "Attempted to create a service which already exists: " |
| + ", clusterName=" + clusterName + " serviceName=" + StringUtils.join(duplicates, ","); |
| |
| throw new DuplicateResourceException(msg); |
| } |
| |
| } |
| } |