/**
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
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.AmbariManagementControllerImpl;
import org.apache.ambari.server.controller.MaintenanceStateHelper;
import org.apache.ambari.server.controller.RequestStatusResponse;
import org.apache.ambari.server.controller.ServiceComponentHostRequest;
import org.apache.ambari.server.controller.ServiceComponentHostResponse;
import org.apache.ambari.server.controller.predicate.AndPredicate;
import org.apache.ambari.server.controller.predicate.EqualsPredicate;
import org.apache.ambari.server.controller.predicate.NotPredicate;
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.orm.dao.HostVersionDAO;
import org.apache.ambari.server.orm.entities.HostVersionEntity;
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.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.MaintenanceState;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.ServiceComponentHost;
import org.apache.ambari.server.state.ServiceComponentHostEvent;
import org.apache.ambari.server.state.State;
import org.apache.ambari.server.state.fsm.InvalidStateTransitionException;
import org.apache.ambari.server.state.svccomphost.ServiceComponentHostDisableEvent;
import org.apache.ambari.server.state.svccomphost.ServiceComponentHostRestoreEvent;
import org.apache.ambari.server.topology.Setting;
import org.apache.commons.lang.StringUtils;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
/**
* Resource provider for host component resources.
*/
public class HostComponentResourceProvider extends AbstractControllerResourceProvider {
// ----- Property ID constants ---------------------------------------------
// Host Components
public static final String HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "cluster_name");
public static final String HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "service_name");
public static final String HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "component_name");
public static final String HOST_COMPONENT_DISPLAY_NAME_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "display_name");
public static final String HOST_COMPONENT_HOST_NAME_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "host_name");
public static final String HOST_COMPONENT_PUBLIC_HOST_NAME_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "public_host_name");
public static final String HOST_COMPONENT_STATE_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "state");
public static final String HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "desired_state");
public static final String HOST_COMPONENT_STACK_ID_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "stack_id");
public static final String HOST_COMPONENT_DESIRED_STACK_ID_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "desired_stack_id");
public static final String HOST_COMPONENT_ACTUAL_CONFIGS_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "actual_configs");
public static final String HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "stale_configs");
public static final String HOST_COMPONENT_DESIRED_ADMIN_STATE_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "desired_admin_state");
public static final String HOST_COMPONENT_MAINTENANCE_STATE_PROPERTY_ID
= "HostRoles/maintenance_state";
public static final String HOST_COMPONENT_HDP_VERSION_PROPERTY_ID
= PropertyHelper.getPropertyId("HostRoles", "hdp_version");
public static final String HOST_COMPONENT_UPGRADE_STATE_PROPERTY_ID = "HostRoles/upgrade_state";
//Parameters from the predicate
private static final String QUERY_PARAMETERS_RUN_SMOKE_TEST_ID = "params/run_smoke_test";
private static Set<String> pkPropertyIds =
new HashSet<>(Arrays.asList(new String[]{
HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID,
HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID,
HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID,
HOST_COMPONENT_HOST_NAME_PROPERTY_ID}));
/**
* maintenance state helper
*/
@Inject
private MaintenanceStateHelper maintenanceStateHelper;
@Inject
private HostVersionDAO hostVersionDAO;
// ----- 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 HostComponentResourceProvider(@Assisted Set<String> propertyIds,
@Assisted Map<Resource.Type, String> keyPropertyIds,
@Assisted AmbariManagementController managementController,
Injector injector) {
super(propertyIds, keyPropertyIds, managementController);
setRequiredCreateAuthorizations(EnumSet.of(RoleAuthorization.SERVICE_ADD_DELETE_SERVICES,RoleAuthorization.HOST_ADD_DELETE_COMPONENTS));
setRequiredDeleteAuthorizations(EnumSet.of(RoleAuthorization.SERVICE_ADD_DELETE_SERVICES,RoleAuthorization.HOST_ADD_DELETE_COMPONENTS));
}
// ----- ResourceProvider ------------------------------------------------
@Override
protected RequestStatus createResourcesAuthorized(Request request)
throws SystemException,
UnsupportedPropertyException,
ResourceAlreadyExistsException,
NoSuchParentResourceException {
final Set<ServiceComponentHostRequest> requests = new HashSet<>();
for (Map<String, Object> propertyMap : request.getProperties()) {
requests.add(changeRequest(propertyMap));
}
createResources(new Command<Void>() {
@Override
public Void invoke() throws AmbariException, AuthorizationException {
getManagementController().createHostComponents(requests);
return null;
}
});
notifyCreate(Resource.Type.HostComponent, request);
return getRequestStatus(null);
}
@Override
public Set<Resource> getResources(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
final Set<ServiceComponentHostRequest> requests = new HashSet<>();
for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
requests.add(getRequest(propertyMap));
}
return findResources(request, predicate, requests);
}
private Set<Resource> getResourcesForUpdate(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
final Set<ServiceComponentHostRequest> requests = new HashSet<>();
for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
requests.add(getRequest(propertyMap));
}
return findResources(request, predicate, requests);
}
private Set<Resource> findResources(Request request, final Predicate predicate,
final Set<ServiceComponentHostRequest> requests)
throws SystemException, NoSuchResourceException, NoSuchParentResourceException {
Set<Resource> resources = new HashSet<>();
Set<String> requestedIds = getRequestPropertyIds(request, predicate);
// We always need host_name for sch
requestedIds.add(HOST_COMPONENT_HOST_NAME_PROPERTY_ID);
Set<ServiceComponentHostResponse> responses = getResources(new Command<Set<ServiceComponentHostResponse>>() {
@Override
public Set<ServiceComponentHostResponse> invoke() throws AmbariException {
return getManagementController().getHostComponents(requests);
}
});
for (ServiceComponentHostResponse response : responses) {
Resource resource = new ResourceImpl(Resource.Type.HostComponent);
setResourceProperty(resource, HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID,
response.getClusterName(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID,
response.getServiceName(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID,
response.getComponentName(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_DISPLAY_NAME_PROPERTY_ID,
response.getDisplayName(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_HOST_NAME_PROPERTY_ID,
response.getHostname(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_PUBLIC_HOST_NAME_PROPERTY_ID,
response.getPublicHostname(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_STATE_PROPERTY_ID,
response.getLiveState(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID,
response.getDesiredState(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_STACK_ID_PROPERTY_ID,
response.getStackVersion(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_DESIRED_STACK_ID_PROPERTY_ID,
response.getDesiredStackVersion(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_ACTUAL_CONFIGS_PROPERTY_ID,
response.getActualConfigs(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID,
response.isStaleConfig(), requestedIds);
setResourceProperty(resource, HOST_COMPONENT_UPGRADE_STATE_PROPERTY_ID,
response.getUpgradeState(), requestedIds);
if (requestedIds.contains(HOST_COMPONENT_HDP_VERSION_PROPERTY_ID)) {
HostVersionEntity versionEntity = hostVersionDAO.
findByHostAndStateCurrent(response.getClusterName(), response.getHostname());
if (versionEntity != null) {
setResourceProperty(resource, HOST_COMPONENT_HDP_VERSION_PROPERTY_ID,
versionEntity.getRepositoryVersion().getDisplayName(), requestedIds);
}
}
if (response.getAdminState() != null) {
setResourceProperty(resource, HOST_COMPONENT_DESIRED_ADMIN_STATE_PROPERTY_ID,
response.getAdminState(), requestedIds);
}
if (null != response.getMaintenanceState()) {
setResourceProperty(resource, HOST_COMPONENT_MAINTENANCE_STATE_PROPERTY_ID,
response.getMaintenanceState(), requestedIds);
}
resources.add(resource);
}
return resources;
}
@Override
public RequestStatus updateResources(final Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
if (request.getProperties().isEmpty()) {
throw new IllegalArgumentException("Received an update request with no properties");
}
RequestStageContainer requestStages = doUpdateResources(null, request, predicate, false);
RequestStatusResponse response = null;
if (requestStages != null) {
try {
requestStages.persist();
} catch (AmbariException e) {
throw new SystemException(e.getMessage(), e);
}
response = requestStages.getRequestStatusResponse();
notifyUpdate(Resource.Type.HostComponent, request, predicate);
}
return getRequestStatus(response);
}
@Override
protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
final Set<ServiceComponentHostRequest> requests = new HashSet<>();
for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
requests.add(changeRequest(propertyMap));
}
DeleteStatusMetaData deleteStatusMetaData = modifyResources(new Command<DeleteStatusMetaData>() {
@Override
public DeleteStatusMetaData invoke() throws AmbariException, AuthorizationException {
return getManagementController().deleteHostComponents(requests);
}
});
notifyDelete(Resource.Type.HostComponent, predicate);
return getRequestStatus(null, null, deleteStatusMetaData);
}
@Override
public Set<String> checkPropertyIds(Set<String> propertyIds) {
propertyIds = super.checkPropertyIds(propertyIds);
if (propertyIds.isEmpty()) {
return propertyIds;
}
Set<String> unsupportedProperties = new HashSet<>();
for (String propertyId : propertyIds) {
if (!propertyId.equals("config")) {
String propertyCategory = PropertyHelper.getPropertyCategory(propertyId);
if (propertyCategory == null || !propertyCategory.equals("config")) {
unsupportedProperties.add(propertyId);
}
}
}
return unsupportedProperties;
}
public RequestStatusResponse install(String cluster, String hostname, Collection<String> skipInstallForComponents, Collection<String> dontSkipInstallForComponents, boolean skipFailure) throws SystemException,
UnsupportedPropertyException, NoSuchParentResourceException {
RequestStageContainer requestStages;
//for (String host : hosts) {
Map<String, Object> installProperties = new HashMap<>();
installProperties.put(HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID, "INSTALLED");
Map<String, String> requestInfo = new HashMap<>();
requestInfo.put("context", String.format("Install components on host %s", hostname));
requestInfo.put("phase", "INITIAL_INSTALL");
requestInfo.put(AmbariManagementControllerImpl.SKIP_INSTALL_FOR_COMPONENTS, StringUtils.join
(skipInstallForComponents, ";"));
requestInfo.put(AmbariManagementControllerImpl.DONT_SKIP_INSTALL_FOR_COMPONENTS, StringUtils.join
(dontSkipInstallForComponents, ";"));
Request installRequest = PropertyHelper.getUpdateRequest(installProperties, requestInfo);
Predicate statePredicate = new EqualsPredicate<>(HOST_COMPONENT_STATE_PROPERTY_ID, "INIT");
Predicate clusterPredicate = new EqualsPredicate<>(HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID, cluster);
// single host
Predicate hostPredicate = new EqualsPredicate<>(HOST_COMPONENT_HOST_NAME_PROPERTY_ID, hostname);
//Predicate hostPredicate = new OrPredicate(hostPredicates.toArray(new Predicate[hostPredicates.size()]));
Predicate hostAndStatePredicate = new AndPredicate(statePredicate, hostPredicate);
Predicate installPredicate = new AndPredicate(hostAndStatePredicate, clusterPredicate);
try {
LOG.info("Installing all components on host: " + hostname);
requestStages = doUpdateResources(null, installRequest, installPredicate, true);
notifyUpdate(Resource.Type.HostComponent, installRequest, installPredicate);
try {
requestStages.persist();
} catch (AmbariException e) {
throw new SystemException(e.getMessage(), e);
}
} catch (NoSuchResourceException e) {
// shouldn't encounter this exception here
throw new SystemException("An unexpected exception occurred while processing install hosts", e);
}
return requestStages.getRequestStatusResponse();
}
// TODO, revisit this extra method, that appears to be used during Add Hosts
// TODO, How do we determine the component list for INSTALL_ONLY during an Add Hosts operation? rwn
public RequestStatusResponse start(String cluster, String hostName) throws SystemException,
UnsupportedPropertyException, NoSuchParentResourceException {
return this.start(cluster, hostName, Collections.<String>emptySet(), false);
}
public RequestStatusResponse start(String cluster, String hostName, Collection<String> installOnlyComponents, boolean skipFailure) throws SystemException,
UnsupportedPropertyException, NoSuchParentResourceException {
Map<String, String> requestInfo = new HashMap<>();
requestInfo.put("context", String.format("Start components on host %s", hostName));
requestInfo.put("phase", "INITIAL_START");
requestInfo.put(Setting.SETTING_NAME_SKIP_FAILURE, Boolean.toString(skipFailure));
Predicate clusterPredicate = new EqualsPredicate<>(HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID, cluster);
Predicate hostPredicate = new EqualsPredicate<>(HOST_COMPONENT_HOST_NAME_PROPERTY_ID, hostName);
//Predicate hostPredicate = new OrPredicate(hostPredicates.toArray(new Predicate[hostPredicates.size()]));
RequestStageContainer requestStages;
try {
Map<String, Object> startProperties = new HashMap<>();
startProperties.put(HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID, "STARTED");
Request startRequest = PropertyHelper.getUpdateRequest(startProperties, requestInfo);
// Important to query against desired_state as this has been updated when install stage was created
// If I query against state, then the getRequest compares predicate prop against desired_state and then when the predicate
// is later applied explicitly, it gets compared to live_state. Since live_state == INSTALLED == INIT at this point and
// desired_state == INSTALLED, we will always get 0 matches since both comparisons can't be true :(
Predicate installedStatePredicate = new EqualsPredicate<>(HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID, "INSTALLED");
Predicate notClientPredicate = new NotPredicate(new ClientComponentPredicate());
Predicate clusterAndClientPredicate = new AndPredicate(clusterPredicate, notClientPredicate);
Predicate hostAndStatePredicate = new AndPredicate(installedStatePredicate, hostPredicate);
Predicate startPredicate;
if (installOnlyComponents.isEmpty()) {
// all installed components should be started
startPredicate = new AndPredicate(clusterAndClientPredicate, hostAndStatePredicate);
LOG.info("Starting all non-client components on host: " + hostName);
} else {
// any INSTALL_ONLY components should not be started
List<Predicate> listOfComponentPredicates =
new ArrayList<>();
for (String installOnlyComponent : installOnlyComponents) {
Predicate componentNameEquals = new EqualsPredicate<>(HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID, installOnlyComponent);
// create predicate to filter out the install only component
listOfComponentPredicates.add(new NotPredicate(componentNameEquals));
}
Predicate[] arrayOfInstallOnlyPredicates = new Predicate[listOfComponentPredicates.size()];
// aggregate Predicate of all INSTALL_ONLY component names
Predicate installOnlyComponentsPredicate = new AndPredicate(listOfComponentPredicates.toArray(arrayOfInstallOnlyPredicates));
// start predicate must now include the INSTALL_ONLY component predicates, in
// order to filter out those components for START attempts
startPredicate = new AndPredicate(clusterAndClientPredicate, hostAndStatePredicate, installOnlyComponentsPredicate);
LOG.info("Starting all non-client components on host: " + hostName + ", except for the INSTALL_ONLY components specified: " + installOnlyComponents);
}
requestStages = doUpdateResources(null, startRequest, startPredicate, true);
notifyUpdate(Resource.Type.HostComponent, startRequest, startPredicate);
try {
requestStages.persist();
} catch (AmbariException e) {
throw new SystemException(e.getMessage(), e);
}
} catch (NoSuchResourceException e) {
// shouldn't encounter this exception here
throw new SystemException("An unexpected exception occurred while processing start hosts", e);
}
return requestStages.getRequestStatusResponse();
}
/**
* Update the host component identified by the given request object with the
* values carried by the given request object.
*
* @param stages stages of the associated request
* @param requests the request object which defines which host component to
* update and the values to set
* @param requestProperties the request properties
* @param runSmokeTest indicates whether or not to run a smoke test
*
* @return a track action response
*
* @throws AmbariException thrown if the resource cannot be updated
*/
//todo: This was moved from AmbariManagementController and needs a lot of refactoring.
//todo: Look into using the predicate instead of Set<ServiceComponentHostRequest>
//todo: change to private access when all AMC tests have been moved.
protected RequestStageContainer updateHostComponents(RequestStageContainer stages,
Set<ServiceComponentHostRequest> requests,
Map<String, String> requestProperties,
boolean runSmokeTest) throws AmbariException, AuthorizationException {
Clusters clusters = getManagementController().getClusters();
Map<String, Map<State, List<ServiceComponentHost>>> changedScHosts = new HashMap<>();
Collection<ServiceComponentHost> ignoredScHosts = new ArrayList<>();
Set<String> clusterNames = new HashSet<>();
Map<String, Map<String, Map<String, Set<String>>>> requestClusters = new HashMap<>();
Map<ServiceComponentHost, State> directTransitionScHosts = new HashMap<>();
Resource.Type reqOpLvl = determineOperationLevel(requestProperties);
String clusterName = requestProperties.get(RequestOperationLevel.OPERATION_CLUSTER_ID);
if (clusterName != null && !clusterName.isEmpty()) {
clusterNames.add(clusterName);
}
for (ServiceComponentHostRequest request : requests) {
validateServiceComponentHostRequest(request);
Cluster cluster = clusters.getCluster(request.getClusterName());
if(runSmokeTest) {
if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, cluster.getResourceId(), RoleAuthorization.SERVICE_RUN_SERVICE_CHECK)) {
throw new AuthorizationException("The authenticated user is not authorized to run service checks");
}
}
if (StringUtils.isEmpty(request.getServiceName())) {
request.setServiceName(getManagementController().findServiceName(cluster, request.getComponentName()));
}
ServiceComponent sc = getServiceComponent(
request.getClusterName(), request.getServiceName(), request.getComponentName());
logRequestInfo("Received a updateHostComponent request", request);
if((clusterName == null || clusterName.isEmpty())
&& (request.getClusterName() != null
&& !request.getClusterName().isEmpty())) {
clusterNames.add(request.getClusterName());
}
if (clusterNames.size() > 1) {
throw new IllegalArgumentException("Updates to multiple clusters is not"
+ " supported");
}
// maps of cluster->services, services->components, components->hosts
Map<String, Map<String, Set<String>>> clusterServices = requestClusters.get(request.getClusterName());
if (clusterServices == null) {
clusterServices = new HashMap<>();
requestClusters.put(request.getClusterName(), clusterServices);
}
Map<String, Set<String>> serviceComponents = clusterServices.get(request.getServiceName());
if (serviceComponents == null) {
serviceComponents = new HashMap<>();
clusterServices.put(request.getServiceName(), serviceComponents);
}
Set<String> componentHosts = serviceComponents.get(request.getComponentName());
if (componentHosts == null) {
componentHosts = new HashSet<>();
serviceComponents.put(request.getComponentName(), componentHosts) ;
}
if (componentHosts.contains(request.getHostname())) {
throw new IllegalArgumentException("Invalid request contains duplicate hostcomponents");
}
componentHosts.add(request.getHostname());
ServiceComponentHost sch = sc.getServiceComponentHost(request.getHostname());
State oldState = sch.getState();
State newState = null;
if (request.getDesiredState() != null) {
// set desired state on host component
newState = State.valueOf(request.getDesiredState());
// throw exception if desired state isn't a valid desired state (static check)
if (!newState.isValidDesiredState()) {
throw new IllegalArgumentException("Invalid arguments, invalid"
+ " desired state, desiredState=" + newState.toString());
}
}
// Setting Maintenance state for host component
if (null != request.getMaintenanceState()) {
MaintenanceState newMaint = MaintenanceState.valueOf(request.getMaintenanceState());
MaintenanceState oldMaint = maintenanceStateHelper.getEffectiveState(sch);
if (newMaint != oldMaint) {
if (sc.isClientComponent()) {
throw new IllegalArgumentException("Invalid arguments, cannot set maintenance state on a client component");
} else 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 {
sch.setMaintenanceState(newMaint);
}
}
}
if (newState == null) {
LOG.info(getServiceComponentRequestInfoLogMessage("Nothing to do for new updateServiceComponentHost", request, oldState, null));
continue;
}
if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, cluster.getResourceId(),
EnumSet.of(RoleAuthorization.SERVICE_START_STOP, RoleAuthorization.SERVICE_ADD_DELETE_SERVICES,
RoleAuthorization.HOST_ADD_DELETE_COMPONENTS, RoleAuthorization.HOST_ADD_DELETE_HOSTS))) {
throw new AuthorizationException("The authenticated user is not authorized to change the state of service components");
}
// STARTED state is invalid for the client component, but this shouldn't cancel the whole stage
if (sc.isClientComponent() && newState == State.STARTED &&
!requestProperties.containsKey(sch.getServiceComponentName().toLowerCase())) {
ignoredScHosts.add(sch);
LOG.info(getServiceComponentRequestInfoLogMessage("Ignoring ServiceComponentHost as STARTED new desired state for client components is not valid", request, sch.getState(), newState));
continue;
}
if (sc.isClientComponent() &&
!newState.isValidClientComponentState()) {
throw new IllegalArgumentException("Invalid desired state for a client"
+ " component");
}
State oldSchState = sch.getState();
// Client component reinstall allowed
if (newState == oldSchState && !sc.isClientComponent() &&
!requestProperties.containsKey(sch.getServiceComponentName().toLowerCase())) {
ignoredScHosts.add(sch);
LOG.info(getServiceComponentRequestInfoLogMessage("Ignoring ServiceComponentHost as the current state matches the new desired state", request, oldState, newState));
continue;
}
if (! maintenanceStateHelper.isOperationAllowed(reqOpLvl, sch)) {
ignoredScHosts.add(sch);
LOG.info(getServiceComponentRequestInfoLogMessage("Ignoring ServiceComponentHost as operation is not allowed", request, oldState, newState));
continue;
}
if (! isValidStateTransition(stages, oldSchState, newState, sch)) {
throw new AmbariException("Invalid state transition for host component"
+ ", clusterName=" + cluster.getClusterName()
+ ", clusterId=" + cluster.getClusterId()
+ ", serviceName=" + sch.getServiceName()
+ ", componentName=" + sch.getServiceComponentName()
+ ", hostname=" + sch.getHostName()
+ ", currentState=" + oldSchState
+ ", newDesiredState=" + newState);
}
if (isDirectTransition(oldSchState, newState)) {
LOG.info(getServiceComponentRequestInfoLogMessage("Handling direct transition update to host component", request, oldState, newState));
directTransitionScHosts.put(sch, newState);
} else {
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>());
}
LOG.info(getServiceComponentRequestInfoLogMessage("Handling update to host component", request, oldState, newState));
changedScHosts.get(sc.getName()).get(newState).add(sch);
}
}
doDirectTransitions(directTransitionScHosts);
// just getting the first cluster
Cluster cluster = clusters.getCluster(clusterNames.iterator().next());
return getManagementController().addStages(
stages, cluster, requestProperties, null, null, null,
changedScHosts, ignoredScHosts, runSmokeTest, false);
}
@Override
protected Set<String> getPKPropertyIds() {
return pkPropertyIds;
}
// ----- utility methods -------------------------------------------------
/**
* Get a component request object from a map of property values.
*
* @param properties the predicate
* @return the component request object
*/
private ServiceComponentHostRequest getRequest(Map<String, Object> properties) {
ServiceComponentHostRequest serviceComponentHostRequest = new ServiceComponentHostRequest(
(String) properties.get(HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_HOST_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID));
serviceComponentHostRequest.setState((String) properties.get(HOST_COMPONENT_STATE_PROPERTY_ID));
serviceComponentHostRequest.setDesiredStackId((String) properties.get(HOST_COMPONENT_STACK_ID_PROPERTY_ID));
if (properties.get(HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID) != null) {
serviceComponentHostRequest.setStaleConfig(
properties.get(HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID).toString().toLowerCase());
}
if (properties.get(HOST_COMPONENT_DESIRED_ADMIN_STATE_PROPERTY_ID) != null) {
serviceComponentHostRequest.setAdminState(
properties.get(HOST_COMPONENT_DESIRED_ADMIN_STATE_PROPERTY_ID).toString());
}
if (properties.get(HOST_COMPONENT_PUBLIC_HOST_NAME_PROPERTY_ID) != null) {
serviceComponentHostRequest.setPublicHostname(
properties.get(HOST_COMPONENT_PUBLIC_HOST_NAME_PROPERTY_ID).toString());
}
Object o = properties.get(HOST_COMPONENT_MAINTENANCE_STATE_PROPERTY_ID);
if (null != o) {
serviceComponentHostRequest.setMaintenanceState (o.toString());
}
return serviceComponentHostRequest;
}
/**
* Put changes to component request object from a map of property values.
*
* @param properties the predicate
* @return the component request object
*/
private ServiceComponentHostRequest changeRequest(Map<String, Object> properties) {
ServiceComponentHostRequest serviceComponentHostRequest = new ServiceComponentHostRequest(
(String) properties.get(HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_HOST_NAME_PROPERTY_ID),
(String) properties.get(HOST_COMPONENT_STATE_PROPERTY_ID));
if (properties.get(HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID) != null) {
serviceComponentHostRequest.setDesiredState((String)properties.get(HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID));
}
serviceComponentHostRequest.setDesiredStackId(
(String) properties.get(HOST_COMPONENT_STACK_ID_PROPERTY_ID));
if (properties.get(HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID) != null) {
serviceComponentHostRequest.setStaleConfig(
properties.get(HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID).toString().toLowerCase());
}
if (properties.get(HOST_COMPONENT_DESIRED_ADMIN_STATE_PROPERTY_ID) != null) {
serviceComponentHostRequest.setAdminState(
properties.get(HOST_COMPONENT_DESIRED_ADMIN_STATE_PROPERTY_ID).toString());
}
Object o = properties.get(HOST_COMPONENT_MAINTENANCE_STATE_PROPERTY_ID);
if (null != o) {
serviceComponentHostRequest.setMaintenanceState (o.toString());
}
return serviceComponentHostRequest;
}
/**
* Update resources.
*
* @param stages request stage container
* @param request request
* @param predicate request predicate
* @param performQueryEvaluation should query be evaluated for matching resource set
* @return
* @throws UnsupportedPropertyException an unsupported property was specified in the request
* @throws SystemException an unknown exception occurred
* @throws NoSuchResourceException the query didn't match any resources
* @throws NoSuchParentResourceException a specified parent resource doesn't exist
*/
private RequestStageContainer doUpdateResources(final RequestStageContainer stages, final Request request,
Predicate predicate, boolean performQueryEvaluation)
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
final Set<ServiceComponentHostRequest> requests = new HashSet<>();
final boolean runSmokeTest = "true".equals(getQueryParameterValue(
QUERY_PARAMETERS_RUN_SMOKE_TEST_ID, predicate));
Set<String> queryIds = Collections.singleton(HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID);
Request queryRequest = PropertyHelper.getReadRequest(queryIds);
// will take care of 404 exception
Set<Resource> matchingResources = getResourcesForUpdate(queryRequest, predicate);
for (Resource queryResource : matchingResources) {
//todo: predicate evaluation was removed for BUG-28737 and the removal of this breaks
//todo: the new "add hosts" api. BUG-4818 is the root cause and needs to be addressed
//todo: and then this predicate evaluation should always be performed and the
//todo: temporary performQueryEvaluation flag hack should be removed.
if (! performQueryEvaluation || predicate.evaluate(queryResource)) {
Map<String, Object> updateRequestProperties = new HashMap<>();
// add props from query resource
updateRequestProperties.putAll(PropertyHelper.getProperties(queryResource));
// add properties from update request
//todo: should we flag value size > 1?
if (request.getProperties() != null && request.getProperties().size() != 0) {
updateRequestProperties.putAll(request.getProperties().iterator().next());
}
requests.add(changeRequest(updateRequestProperties));
}
}
if (requests.isEmpty()) {
String msg = String.format("Skipping updating hosts: no matching requests for %s", predicate);
LOG.info(msg);
throw new NoSuchResourceException(msg);
}
RequestStageContainer requestStages = modifyResources(new Command<RequestStageContainer>() {
@Override
public RequestStageContainer invoke() throws AmbariException {
RequestStageContainer stageContainer = null;
int retriesRemaining = 100;
do {
try {
stageContainer = updateHostComponents(stages, requests, request.getRequestInfoProperties(),
runSmokeTest);
} catch (Exception e) {
if (--retriesRemaining == 0) {
LOG.info("Caught an exception while updating host components, will not try again: {}", e.getMessage(), e);
// !!! IllegalArgumentException results in a 400 response, RuntimeException results in 500.
if (IllegalArgumentException.class.isInstance(e)) {
throw (IllegalArgumentException) e;
} else {
throw new RuntimeException("Update Host request submission failed: " + e, e);
}
} else {
LOG.info("Caught an exception while updating host components, retrying : " + e);
try {
Thread.sleep(250);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Update Host request submission failed: " + e, e);
}
}
}
} while (stageContainer == null);
return stageContainer;
}
});
notifyUpdate(Resource.Type.HostComponent, request, predicate);
return requestStages;
}
/**
* Determine whether a host component state change is valid.
* Looks at projected state from the current stages associated with the request.
*
*
* @param stages request stages
* @param startState host component start state
* @param desiredState host component 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) {
//todo: After separating install and start, the install stage is no longer included in the passed in request stage container
//todo: so we need to re-evaluate this getting projected state from topology manager
return true;
// if (stages != null) {
// State projectedState = stages.getProjectedState(host.getHostName(), host.getServiceComponentName());
// startState = projectedState == null ? startState : projectedState;
// }
//
// return State.isValidStateTransition(startState, desiredState);
}
/**
* Checks if assigning new state does not require performing
* any additional actions
*/
public boolean isDirectTransition(State oldState, State newState) {
switch (newState) {
case INSTALLED:
if (oldState == State.DISABLED) {
return true;
}
break;
case DISABLED:
if (oldState == State.INSTALLED ||
oldState == State.INSTALL_FAILED ||
oldState == State.UNKNOWN) {
return true;
}
break;
default:
break;
}
return false;
}
private ServiceComponent getServiceComponent(String clusterName, String serviceName, String componentName)
throws AmbariException {
Clusters clusters = getManagementController().getClusters();
return clusters.getCluster(clusterName).getService(serviceName).getServiceComponent(componentName);
}
// Perform direct transitions (without task generation)
private void doDirectTransitions(Map<ServiceComponentHost, State> directTransitionScHosts) throws AmbariException {
for (Map.Entry<ServiceComponentHost, State> entry : directTransitionScHosts.entrySet()) {
ServiceComponentHost componentHost = entry.getKey();
State newState = entry.getValue();
long timestamp = System.currentTimeMillis();
ServiceComponentHostEvent event;
componentHost.setDesiredState(newState);
switch (newState) {
case DISABLED:
event = new ServiceComponentHostDisableEvent(
componentHost.getServiceComponentName(),
componentHost.getHostName(),
timestamp);
break;
case INSTALLED:
event = new ServiceComponentHostRestoreEvent(
componentHost.getServiceComponentName(),
componentHost.getHostName(),
timestamp);
break;
default:
throw new AmbariException("Direct transition from " + componentHost.getState() + " to " + newState + " not supported");
}
try {
componentHost.handleEvent(event);
} catch (InvalidStateTransitionException e) {
//Should not occur, must be covered by previous checks
throw new AmbariException("Internal error - not supported transition", e);
}
}
}
/**
* Logs request info.
*
* @param msg base log msg
* @param request the request to log
*/
private void logRequestInfo(String msg, ServiceComponentHostRequest request) {
LOG.info("{}, clusterName={}, serviceName={}, componentName={}, hostname={}, request={}",
msg,
request.getClusterName(),
request.getServiceName(),
request.getComponentName(),
request.getHostname(),
request);
}
/**
* Constructs INFO level log message for {@link ServiceComponentHostRequest}
* @param msg base message
* @param request the request to construct the log message for
* @param oldState current state of the service host component that the request is for.
* @param newDesiredState new desired state for the service host component
*/
private String getServiceComponentRequestInfoLogMessage(String msg, ServiceComponentHostRequest request, State oldState, State newDesiredState) {
StringBuilder sb = new StringBuilder();
sb.append(msg)
.append(", clusterName=").append(request.getClusterName())
.append(", serviceName=").append(request.getServiceName())
.append(", componentName=").append(request.getComponentName())
.append(", hostname=").append(request.getHostname())
.append(", currentState=").append(oldState == null ? "null" : oldState)
.append(", newDesiredState=").append(newDesiredState == null ? "null" : newDesiredState);
return sb.toString();
}
/**
* Get the "operation level" from the request.
*
* @param requestProperties request properties
* @return the "operation level"
*/
private Resource.Type determineOperationLevel(Map<String, String> requestProperties) {
// Determine operation level
Resource.Type reqOpLvl;
if (requestProperties.containsKey(RequestOperationLevel.OPERATION_LEVEL_ID)) {
reqOpLvl = new RequestOperationLevel(requestProperties).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;
}
return reqOpLvl;
}
/**
* Validate a host component request.
*
* @param request request to validate
* @throws IllegalArgumentException if the request is invalid
*/
private void validateServiceComponentHostRequest(ServiceComponentHostRequest request) {
if (request.getClusterName() == null
|| request.getClusterName().isEmpty()
|| request.getComponentName() == null
|| request.getComponentName().isEmpty()
|| request.getHostname() == null
|| request.getHostname().isEmpty()) {
throw new IllegalArgumentException("Invalid arguments"
+ ", cluster name, component name and host name should be"
+ " provided");
}
if (request.getAdminState() != null) {
throw new IllegalArgumentException("Property adminState cannot be modified through update. Use service " +
"specific DECOMMISSION action to decommision/recommission components.");
}
}
// ----- inner classes ---------------------------------------------------
/**
* Predicate that identifies client components.
*/
private class ClientComponentPredicate implements Predicate {
@Override
public boolean evaluate(Resource resource) {
boolean isClient = false;
String componentName = (String) resource.getPropertyValue(HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID);
try {
if (componentName != null && !componentName.isEmpty()) {
AmbariManagementController managementController = getManagementController();
String clusterName = (String) resource.getPropertyValue(HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID);
String serviceName = (String) resource.getPropertyValue(HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID);
if (StringUtils.isEmpty(serviceName)) {
Cluster cluster = managementController.getClusters().getCluster(clusterName);
serviceName = managementController.findServiceName(cluster, componentName);
}
ServiceComponent sc = getServiceComponent((String) resource.getPropertyValue(
HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID), serviceName, componentName);
isClient = sc.isClientComponent();
}
} catch (AmbariException e) {
// this is really a system exception since cluster/service should have been already verified
throw new RuntimeException(
"An unexpected exception occurred while trying to determine if a component is a client", e);
}
return isClient;
}
}
}