/* * 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.text.MessageFormat; import java.util.ArrayList; 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.ClusterNotFoundException; import org.apache.ambari.server.DuplicateResourceException; import org.apache.ambari.server.HostNotFoundException; import org.apache.ambari.server.ObjectNotFoundException; import org.apache.ambari.server.ParentObjectNotFoundException; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.ConfigurationRequest; import org.apache.ambari.server.controller.HostRequest; import org.apache.ambari.server.controller.HostResponse; 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.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.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.Config; import org.apache.ambari.server.state.DesiredConfig; import org.apache.ambari.server.state.Host; import org.apache.ambari.server.state.MaintenanceState; import org.apache.ambari.server.state.ServiceComponentHost; import org.apache.ambari.server.state.State; import org.apache.ambari.server.state.stack.OsFamily; import org.apache.ambari.server.topology.ClusterTopology; import org.apache.ambari.server.topology.InvalidTopologyException; import org.apache.ambari.server.topology.InvalidTopologyTemplateException; import org.apache.ambari.server.topology.LogicalRequest; import org.apache.ambari.server.topology.TopologyManager; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import com.google.inject.persist.Transactional; /** * Resource provider for host resources. */ public class HostResourceProvider extends AbstractControllerResourceProvider { // ----- Property ID constants --------------------------------------------- // Hosts public static final String HOST_CLUSTER_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "cluster_name"); public static final String HOST_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "host_name"); public static final String HOST_PUBLIC_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "public_host_name"); public static final String HOST_IP_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "ip"); public static final String HOST_TOTAL_MEM_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "total_mem"); public static final String HOST_CPU_COUNT_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "cpu_count"); public static final String HOST_PHYSICAL_CPU_COUNT_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "ph_cpu_count"); public static final String HOST_OS_ARCH_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "os_arch"); public static final String HOST_OS_TYPE_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "os_type"); public static final String HOST_OS_FAMILY_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "os_family"); public static final String HOST_RACK_INFO_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "rack_info"); public static final String HOST_LAST_HEARTBEAT_TIME_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "last_heartbeat_time"); public static final String HOST_LAST_REGISTRATION_TIME_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "last_registration_time"); public static final String HOST_DISK_INFO_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "disk_info"); public static final String HOST_HOST_STATUS_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "host_status"); public static final String HOST_MAINTENANCE_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "maintenance_state"); public static final String HOST_HOST_HEALTH_REPORT_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "host_health_report"); public static final String HOST_RECOVERY_REPORT_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "recovery_report"); public static final String HOST_RECOVERY_SUMMARY_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "recovery_summary"); public static final String HOST_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "host_state"); public static final String HOST_LAST_AGENT_ENV_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "last_agent_env"); public static final String HOST_DESIRED_CONFIGS_PROPERTY_ID = PropertyHelper.getPropertyId("Hosts", "desired_configs"); public static final String BLUEPRINT_PROPERTY_ID = PropertyHelper.getPropertyId(null, "blueprint"); public static final String HOSTGROUP_PROPERTY_ID = PropertyHelper.getPropertyId(null, "host_group"); public static final String HOST_NAME_NO_CATEGORY_PROPERTY_ID = PropertyHelper.getPropertyId(null, "host_name"); public static final String HOST_COUNT_PROPERTY_ID = PropertyHelper.getPropertyId(null, "host_count"); public static final String HOST_PREDICATE_PROPERTY_ID = PropertyHelper.getPropertyId(null, "host_predicate"); //todo use the same json structure for cluster host addition (cluster template and upscale) public static final String HOST_RACK_INFO_NO_CATEGORY_PROPERTY_ID = PropertyHelper.getPropertyId(null, "rack_info"); protected static final String FORCE_DELETE_COMPONENTS = "force_delete_components"; private static Set<String> pkPropertyIds = new HashSet<>(Arrays.asList(new String[]{ HOST_NAME_PROPERTY_ID})); @Inject private MaintenanceStateHelper maintenanceStateHelper; @Inject private OsFamily osFamily; @Inject private static TopologyManager topologyManager; // ----- 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 HostResourceProvider(@Assisted Set<String> propertyIds, @Assisted Map<Resource.Type, String> keyPropertyIds, @Assisted AmbariManagementController managementController) { super(propertyIds, keyPropertyIds, managementController); Set<RoleAuthorization> authorizationsAddDelete = EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_HOSTS); setRequiredCreateAuthorizations(authorizationsAddDelete); setRequiredDeleteAuthorizations(authorizationsAddDelete); setRequiredGetAuthorizations(RoleAuthorization.AUTHORIZATIONS_VIEW_CLUSTER); setRequiredUpdateAuthorizations(RoleAuthorization.AUTHORIZATIONS_UPDATE_CLUSTER); } // ----- ResourceProvider ------------------------------------------------ @Override protected RequestStatus createResourcesAuthorized(final Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException { RequestStatusResponse createResponse = null; if (isHostGroupRequest(request)) { createResponse = submitHostRequests(request); } else { createResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { createHosts(request); return null; } }); } notifyCreate(Resource.Type.Host, request); return getRequestStatus(createResponse); } @Override protected Set<Resource> getResourcesAuthorized(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { final Set<HostRequest> requests = new HashSet<>(); if (predicate == null) { requests.add(getRequest(null)); } else { for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { requests.add(getRequest(propertyMap)); } } Set<HostResponse> responses = getResources(new Command<Set<HostResponse>>() { @Override public Set<HostResponse> invoke() throws AmbariException { return getHosts(requests); } }); Set<String> requestedIds = getRequestPropertyIds(request, predicate); Set<Resource> resources = new HashSet<>(); for (HostResponse response : responses) { Resource resource = new ResourceImpl(Resource.Type.Host); // TODO : properly handle more than one cluster if (response.getClusterName() != null && !response.getClusterName().isEmpty()) { setResourceProperty(resource, HOST_CLUSTER_NAME_PROPERTY_ID, response.getClusterName(), requestedIds); } setResourceProperty(resource, HOST_NAME_PROPERTY_ID, response.getHostname(), requestedIds); setResourceProperty(resource, HOST_PUBLIC_NAME_PROPERTY_ID, response.getPublicHostName(), requestedIds); setResourceProperty(resource, HOST_IP_PROPERTY_ID, response.getIpv4(), requestedIds); setResourceProperty(resource, HOST_TOTAL_MEM_PROPERTY_ID, response.getTotalMemBytes(), requestedIds); setResourceProperty(resource, HOST_CPU_COUNT_PROPERTY_ID, (long) response.getCpuCount(), requestedIds); setResourceProperty(resource, HOST_PHYSICAL_CPU_COUNT_PROPERTY_ID, (long) response.getPhCpuCount(), requestedIds); setResourceProperty(resource, HOST_OS_ARCH_PROPERTY_ID, response.getOsArch(), requestedIds); setResourceProperty(resource, HOST_OS_TYPE_PROPERTY_ID, response.getOsType(), requestedIds); String hostOsFamily = osFamily.find(response.getOsType()); if (hostOsFamily == null) { LOG.error("Can not find host OS family. For OS type = '{}' and host name = '{}'", response.getOsType(), response.getHostname()); } setResourceProperty(resource, HOST_OS_FAMILY_PROPERTY_ID, hostOsFamily, requestedIds); setResourceProperty(resource, HOST_RACK_INFO_PROPERTY_ID, response.getRackInfo(), requestedIds); setResourceProperty(resource, HOST_LAST_HEARTBEAT_TIME_PROPERTY_ID, response.getLastHeartbeatTime(), requestedIds); setResourceProperty(resource, HOST_LAST_AGENT_ENV_PROPERTY_ID, response.getLastAgentEnv(), requestedIds); setResourceProperty(resource, HOST_LAST_REGISTRATION_TIME_PROPERTY_ID, response.getLastRegistrationTime(), requestedIds); setResourceProperty(resource, HOST_HOST_STATUS_PROPERTY_ID, response.getStatus(),requestedIds); setResourceProperty(resource, HOST_HOST_HEALTH_REPORT_PROPERTY_ID, response.getHealthStatus().getHealthReport(), requestedIds); setResourceProperty(resource, HOST_RECOVERY_REPORT_PROPERTY_ID, response.getRecoveryReport(), requestedIds); setResourceProperty(resource, HOST_RECOVERY_SUMMARY_PROPERTY_ID, response.getRecoverySummary(), requestedIds); setResourceProperty(resource, HOST_DISK_INFO_PROPERTY_ID, response.getDisksInfo(), requestedIds); setResourceProperty(resource, HOST_STATE_PROPERTY_ID, response.getHostState(), requestedIds); setResourceProperty(resource, HOST_DESIRED_CONFIGS_PROPERTY_ID, response.getDesiredHostConfigs(), requestedIds); // only when a cluster request if (null != response.getMaintenanceState()) { setResourceProperty(resource, HOST_MAINTENANCE_STATE_PROPERTY_ID, response.getMaintenanceState(), requestedIds); } resources.add(resource); } return resources; } @Override protected RequestStatus updateResourcesAuthorized(final Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { final Set<HostRequest> requests = new HashSet<>(); for (Map<String, Object> propertyMap : getPropertyMaps(request.getProperties().iterator().next(), predicate)) { requests.add(getRequest(propertyMap)); } modifyResources(new Command<Void>() { @Override public Void invoke() throws AmbariException, AuthorizationException { updateHosts(requests); return null; } }); notifyUpdate(Resource.Type.Host, request, predicate); return getRequestStatus(null); } @Override protected RequestStatus deleteResourcesAuthorized(final Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { final Set<HostRequest> requests = new HashSet<>(); Map<String, String> requestInfoProperties = request.getRequestInfoProperties(); final boolean forceDelete = requestInfoProperties.containsKey(FORCE_DELETE_COMPONENTS) && requestInfoProperties.get(FORCE_DELETE_COMPONENTS).equals("true"); for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { requests.add(getRequest(propertyMap)); } DeleteStatusMetaData deleteStatusMetaData = modifyResources(new Command<DeleteStatusMetaData>() { @Override public DeleteStatusMetaData invoke() throws AmbariException { return deleteHosts(requests, request.isDryRunRequest(), forceDelete); } }); if (!request.isDryRunRequest()) { notifyDelete(Resource.Type.Host, predicate); } return getRequestStatus(null, null, deleteStatusMetaData); } @Override public Set<String> checkPropertyIds(Set<String> propertyIds) { Set<String> baseUnsupported = super.checkPropertyIds(propertyIds); baseUnsupported.remove(BLUEPRINT_PROPERTY_ID); baseUnsupported.remove(HOSTGROUP_PROPERTY_ID); baseUnsupported.remove(HOST_NAME_NO_CATEGORY_PROPERTY_ID); //todo: constants baseUnsupported.remove(HOST_COUNT_PROPERTY_ID); baseUnsupported.remove(HOST_PREDICATE_PROPERTY_ID); baseUnsupported.remove(HOST_RACK_INFO_NO_CATEGORY_PROPERTY_ID); return checkConfigPropertyIds(baseUnsupported, "Hosts"); } // ----- AbstractResourceProvider ------------------------------------------ @Override protected Set<String> getPKPropertyIds() { return pkPropertyIds; } // ----- utility methods --------------------------------------------------- /** * Determine if a request is a high level "add hosts" call or a simple lower level request * to add a host resources. * * @param request current request * @return true if this is a high level "add hosts" request; * false if it is a simple create host resources request */ private boolean isHostGroupRequest(Request request) { boolean isHostGroupRequest = false; Set<Map<String, Object>> properties = request.getProperties(); if (properties != null && ! properties.isEmpty()) { //todo: for now, either all or none of the hosts need to specify a hg. Unable to mix. String hgName = (String) properties.iterator().next().get(HOSTGROUP_PROPERTY_ID); isHostGroupRequest = hgName != null && ! hgName.isEmpty(); } return isHostGroupRequest; } /** * Get a host request object from a map of property values. * * @param properties the predicate * * @return the component request object */ private HostRequest getRequest(Map<String, Object> properties) { if (properties == null) { return new HostRequest(null, null, null); } HostRequest hostRequest = new HostRequest( getHostNameFromProperties(properties), (String) properties.get(HOST_CLUSTER_NAME_PROPERTY_ID), null); hostRequest.setPublicHostName((String) properties.get(HOST_PUBLIC_NAME_PROPERTY_ID)); String rackInfo = (String) ((null != properties.get(HOST_RACK_INFO_PROPERTY_ID))? properties.get(HOST_RACK_INFO_PROPERTY_ID): properties.get(HOST_RACK_INFO_NO_CATEGORY_PROPERTY_ID)); hostRequest.setRackInfo(rackInfo); hostRequest.setBlueprintName((String) properties.get(BLUEPRINT_PROPERTY_ID)); hostRequest.setHostGroupName((String) properties.get(HOSTGROUP_PROPERTY_ID)); Object o = properties.get(HOST_MAINTENANCE_STATE_PROPERTY_ID); if (null != o) { hostRequest.setMaintenanceState(o.toString()); } List<ConfigurationRequest> cr = getConfigurationRequests("Hosts", properties); hostRequest.setDesiredConfigs(cr); return hostRequest; } /** * Accepts a request with registered hosts and if the request contains a cluster name then will map all of the * hosts onto that cluster. * @param request Request that must contain registered hosts, and optionally a cluster. * @throws AmbariException */ public synchronized void createHosts(Request request) throws AmbariException { Set<Map<String, Object>> propertySet = request.getProperties(); if (propertySet == null || propertySet.isEmpty()) { LOG.warn("Received a create host request with no associated property sets"); return; } AmbariManagementController controller = getManagementController(); Clusters clusters = controller.getClusters(); Set<String> duplicates = new HashSet<>(); Set<String> unknowns = new HashSet<>(); Set<String> allHosts = new HashSet<>(); Set<HostRequest> hostRequests = new HashSet<>(); for (Map<String, Object> propertyMap : propertySet) { HostRequest hostRequest = getRequest(propertyMap); hostRequests.add(hostRequest); if (! propertyMap.containsKey(HOSTGROUP_PROPERTY_ID)) { createHostResource(clusters, duplicates, unknowns, allHosts, hostRequest); } } if (!duplicates.isEmpty()) { StringBuilder names = new StringBuilder(); boolean first = true; for (String hName : duplicates) { if (!first) { names.append(","); } first = false; names.append(hName); } throw new IllegalArgumentException("Invalid request contains" + " duplicate hostnames" + ", hostnames=" + names.toString()); } if (!unknowns.isEmpty()) { StringBuilder names = new StringBuilder(); boolean first = true; for (String hName : unknowns) { if (!first) { names.append(","); } first = false; names.append(hName); } throw new IllegalArgumentException("Attempted to add unknown hosts to a cluster. " + "These hosts have not been registered with the server: " + names.toString()); } Map<String, Set<String>> hostClustersMap = new HashMap<>(); Map<String, Map<String, String>> hostAttributes = new HashMap<>(); Set<String> allClusterSet = new HashSet<>(); for (HostRequest hostRequest : hostRequests) { if (hostRequest.getHostname() != null && !hostRequest.getHostname().isEmpty() && hostRequest.getClusterName() != null && !hostRequest.getClusterName().isEmpty()){ Set<String> clusterSet = new HashSet<>(); clusterSet.add(hostRequest.getClusterName()); allClusterSet.add(hostRequest.getClusterName()); hostClustersMap.put(hostRequest.getHostname(), clusterSet); if (hostRequest.getHostAttributes() != null) { hostAttributes.put(hostRequest.getHostname(), hostRequest.getHostAttributes()); } } } clusters.updateHostWithClusterAndAttributes(hostClustersMap, hostAttributes); for (String clusterName : allClusterSet) { clusters.getCluster(clusterName).recalculateAllClusterVersionStates(); } } private void createHostResource(Clusters clusters, Set<String> duplicates, Set<String> unknowns, Set<String> allHosts, HostRequest request) throws AmbariException { if (request.getHostname() == null || request.getHostname().isEmpty()) { throw new IllegalArgumentException("Invalid arguments, hostname" + " cannot be null"); } if (LOG.isDebugEnabled()) { LOG.debug("Received a createHost request" + ", hostname=" + request.getHostname() + ", request=" + request); } if (allHosts.contains(request.getHostname())) { // throw dup error later duplicates.add(request.getHostname()); return; } allHosts.add(request.getHostname()); try { // ensure host is registered clusters.getHost(request.getHostname()); } catch (HostNotFoundException e) { unknowns.add(request.getHostname()); return; } if (request.getClusterName() != null) { try { // validate that cluster_name is valid clusters.getCluster(request.getClusterName()); } catch (ClusterNotFoundException e) { throw new ParentObjectNotFoundException("Attempted to add a host to a cluster which doesn't exist: " + " clusterName=" + request.getClusterName()); } } } public RequestStatusResponse install(final String cluster, final String hostname, Collection<String> skipInstallForComponents, Collection<String> dontSkipInstallForComponents, final boolean skipFailure) throws ResourceAlreadyExistsException, SystemException, NoSuchParentResourceException, UnsupportedPropertyException { return ((HostComponentResourceProvider) getResourceProvider(Resource.Type.HostComponent)). install(cluster, hostname, skipInstallForComponents, dontSkipInstallForComponents, skipFailure); } public RequestStatusResponse start(final String cluster, final String hostname) throws ResourceAlreadyExistsException, SystemException, NoSuchParentResourceException, UnsupportedPropertyException { return ((HostComponentResourceProvider) getResourceProvider(Resource.Type.HostComponent)). start(cluster, hostname); } protected Set<HostResponse> getHosts(Set<HostRequest> requests) throws AmbariException { Set<HostResponse> response = new HashSet<>(); AmbariManagementController controller = getManagementController(); for (HostRequest request : requests) { try { response.addAll(getHosts(controller, request)); } catch (HostNotFoundException 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; } protected static Set<HostResponse> getHosts(AmbariManagementController controller, HostRequest request) throws AmbariException { //TODO/FIXME host can only belong to a single cluster so get host directly from Cluster //TODO/FIXME what is the requirement for filtering on host attributes? List<Host> hosts; Set<HostResponse> response = new HashSet<>(); Cluster cluster = null; Clusters clusters = controller.getClusters(); String clusterName = request.getClusterName(); String hostName = request.getHostname(); if (clusterName != null) { //validate that cluster exists, throws exception if it doesn't. try { cluster = clusters.getCluster(clusterName); } catch (ObjectNotFoundException e) { throw new ParentObjectNotFoundException("Parent Cluster resource doesn't exist", e); } } if (hostName == null) { hosts = clusters.getHosts(); } else { hosts = new ArrayList<>(); try { hosts.add(clusters.getHost(request.getHostname())); } catch (HostNotFoundException e) { // add cluster name throw new HostNotFoundException(clusterName, hostName); } } // retrieve the cluster desired configs once instead of per host Map<String, DesiredConfig> desiredConfigs = null; if (null != cluster) { desiredConfigs = cluster.getDesiredConfigs(); } for (Host h : hosts) { if (clusterName != null) { if (clusters.getClustersForHost(h.getHostName()).contains(cluster)) { HostResponse r = h.convertToResponse(); r.setClusterName(clusterName); r.setDesiredHostConfigs(h.getDesiredHostConfigs(cluster, desiredConfigs)); r.setMaintenanceState(h.getMaintenanceState(cluster.getClusterId())); response.add(r); } else if (hostName != null) { throw new HostNotFoundException(clusterName, hostName); } } else { HostResponse r = h.convertToResponse(); Set<Cluster> clustersForHost = clusters.getClustersForHost(h.getHostName()); //todo: host can only belong to a single cluster if (clustersForHost != null && clustersForHost.size() != 0) { Cluster clusterForHost = clustersForHost.iterator().next(); r.setClusterName(clusterForHost.getClusterName()); r.setDesiredHostConfigs(h.getDesiredHostConfigs(clusterForHost, null)); r.setMaintenanceState(h.getMaintenanceState(clusterForHost.getClusterId())); } response.add(r); } } return response; } protected synchronized void updateHosts(Set<HostRequest> requests) throws AmbariException, AuthorizationException { if (requests.isEmpty()) { LOG.warn("Received an empty requests set"); return; } AmbariManagementController controller = getManagementController(); Clusters clusters = controller.getClusters(); for (HostRequest request : requests) { if (request.getHostname() == null || request.getHostname().isEmpty()) { throw new IllegalArgumentException("Invalid arguments, hostname should be provided"); } } for (HostRequest request : requests) { if (LOG.isDebugEnabled()) { LOG.debug("Received an updateHost request" + ", hostname=" + request.getHostname() + ", request=" + request); } Host host = clusters.getHost(request.getHostname()); String clusterName = request.getClusterName(); Cluster cluster = clusters.getCluster(clusterName); Long clusterId = cluster.getClusterId(); Long resourceId = cluster.getResourceId(); try { // The below method call throws an exception when trying to create a duplicate mapping in the clusterhostmapping // table. This is done to detect duplicates during host create. In order to be robust, handle these gracefully. clusters.mapAndPublishHostsToCluster(new HashSet<>(Arrays.asList(request.getHostname())), clusterName); } catch (DuplicateResourceException e) { // do nothing } if (null != request.getHostAttributes()) { if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, resourceId, RoleAuthorization.HOST_ADD_DELETE_HOSTS)) { throw new AuthorizationException("The authenticated user is not authorized to update host attributes"); } host.setHostAttributes(request.getHostAttributes()); } String rackInfo = host.getRackInfo(); String requestRackInfo = request.getRackInfo(); boolean rackChange = requestRackInfo != null && !requestRackInfo.equals(rackInfo); if (rackChange) { if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, resourceId, RoleAuthorization.HOST_ADD_DELETE_HOSTS)) { throw new AuthorizationException("The authenticated user is not authorized to update host rack information"); } host.setRackInfo(requestRackInfo); } if (null != request.getPublicHostName()) { if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, resourceId, RoleAuthorization.HOST_ADD_DELETE_HOSTS)) { throw new AuthorizationException("The authenticated user is not authorized to update host attributes"); } host.setPublicHostName(request.getPublicHostName()); } if (null != clusterName && null != request.getMaintenanceState()) { if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, resourceId, RoleAuthorization.HOST_TOGGLE_MAINTENANCE)) { throw new AuthorizationException("The authenticated user is not authorized to update host maintenance state"); } MaintenanceState newState = MaintenanceState.valueOf(request.getMaintenanceState()); MaintenanceState oldState = host.getMaintenanceState(clusterId); if (!newState.equals(oldState)) { if (newState.equals(MaintenanceState.IMPLIED_FROM_HOST) || newState.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 { host.setMaintenanceState(clusterId, newState); } } } // Create configurations if (null != clusterName && null != request.getDesiredConfigs()) { if (clusters.getHostsForCluster(clusterName).containsKey(host.getHostName())) { for (ConfigurationRequest cr : request.getDesiredConfigs()) { if (null != cr.getProperties() && cr.getProperties().size() > 0) { LOG.info(MessageFormat.format("Applying configuration with tag ''{0}'' to host ''{1}'' in cluster ''{2}''", cr.getVersionTag(), request.getHostname(), clusterName)); cr.setClusterName(cluster.getClusterName()); controller.createConfiguration(cr); } Config baseConfig = cluster.getConfig(cr.getType(), cr.getVersionTag()); if (null != baseConfig) { String authName = controller.getAuthName(); DesiredConfig oldConfig = host.getDesiredConfigs(clusterId).get(cr.getType()); if (host.addDesiredConfig(clusterId, cr.isSelected(), authName, baseConfig)) { Logger logger = LoggerFactory.getLogger("configchange"); logger.info("cluster '" + cluster.getClusterName() + "', " + "host '" + host.getHostName() + "' " + "changed by: '" + authName + "'; " + "type='" + baseConfig.getType() + "' " + "version='" + baseConfig.getVersion() + "'" + "tag='" + baseConfig.getTag() + "'" + (null == oldConfig ? "" : ", from='" + oldConfig.getTag() + "'")); } } } } } if (clusterName != null && !clusterName.isEmpty()) { clusters.getCluster(clusterName).recalculateAllClusterVersionStates(); if (rackChange) { // Authorization check for this update was performed before we got to this point. controller.registerRackChange(clusterName); } } //todo: if attempt was made to update a property other than those //todo: that are allowed above, should throw exception } } @Transactional protected DeleteStatusMetaData deleteHosts(Set<HostRequest> requests, boolean dryRun, boolean forceDelete) throws AmbariException { AmbariManagementController controller = getManagementController(); Clusters clusters = controller.getClusters(); DeleteStatusMetaData deleteStatusMetaData = new DeleteStatusMetaData(); List<HostRequest> okToRemove = new ArrayList<>(); for (HostRequest hostRequest : requests) { String hostName = hostRequest.getHostname(); if (null == hostName) { continue; } try { validateHostInDeleteFriendlyState(hostRequest, clusters, forceDelete); okToRemove.add(hostRequest); } catch (Exception ex) { deleteStatusMetaData.addException(hostName, ex); } } //If dry run, don't delete. just assume it can be successfully deleted. if (dryRun) { for (HostRequest request : okToRemove) { deleteStatusMetaData.addDeletedKey(request.getHostname()); } } else { processDeleteHostRequests(okToRemove, clusters, deleteStatusMetaData); } //Do not break behavior for existing clients where delete request contains only 1 host. //Response for these requests will have empty body with appropriate error code. //dryRun is a new feature so its ok to unify the behavior if (!dryRun) { if (deleteStatusMetaData.getDeletedKeys().size() + deleteStatusMetaData.getExceptionForKeys().size() == 1) { if (deleteStatusMetaData.getDeletedKeys().size() == 1) { return null; } for (Map.Entry<String, Exception> entry : deleteStatusMetaData.getExceptionForKeys().entrySet()) { Exception ex = entry.getValue(); if (ex instanceof AmbariException) { throw (AmbariException) ex; } else { throw new AmbariException(ex.getMessage(), ex); } } } } return deleteStatusMetaData; } private void processDeleteHostRequests(List<HostRequest> requests, Clusters clusters, DeleteStatusMetaData deleteStatusMetaData) throws AmbariException { Set<String> hostsClusters = new HashSet<>(); Set<String> hostNames = new HashSet<>(); Set<Cluster> allClustersWithHosts = new HashSet<>(); for (HostRequest hostRequest : requests) { // Assume the user also wants to delete it entirely, including all clusters. String hostname = hostRequest.getHostname(); hostNames.add(hostname); if (hostRequest.getClusterName() != null) { hostsClusters.add(hostRequest.getClusterName()); } LOG.info("Received Delete request for host {} from cluster {}.", hostname, hostRequest.getClusterName()); // delete all host components Set<ServiceComponentHostRequest> schrs = new HashSet<>(); for (Cluster cluster : clusters.getClustersForHost(hostname)) { List<ServiceComponentHost> list = cluster.getServiceComponentHosts(hostname); for (ServiceComponentHost sch : list) { ServiceComponentHostRequest schr = new ServiceComponentHostRequest(cluster.getClusterName(), sch.getServiceName(), sch.getServiceComponentName(), sch.getHostName(), null); schrs.add(schr); } } DeleteStatusMetaData componentDeleteStatus = null; if (schrs.size() > 0) { try { componentDeleteStatus = getManagementController().deleteHostComponents(schrs); } catch (Exception ex) { deleteStatusMetaData.addException(hostname, ex); } } if (componentDeleteStatus != null) { for (String key : componentDeleteStatus.getDeletedKeys()) { deleteStatusMetaData.addDeletedKey(key); } for (String key : componentDeleteStatus.getExceptionForKeys().keySet()) { deleteStatusMetaData.addException(key, componentDeleteStatus.getExceptionForKeys().get(key)); } } if (hostRequest.getClusterName() != null) { hostsClusters.add(hostRequest.getClusterName()); } try { clusters.deleteHost(hostname); deleteStatusMetaData.addDeletedKey(hostname); } catch (Exception ex) { deleteStatusMetaData.addException(hostname, ex); } removeHostFromClusterTopology(clusters, hostRequest); for (LogicalRequest logicalRequest: topologyManager.getRequests(Collections.<Long>emptyList())) { logicalRequest.removeHostRequestByHostName(hostname); } } clusters.publishHostsDeletion(allClustersWithHosts, hostNames); for (String clustername : hostsClusters) { clusters.getCluster(clustername).recalculateAllClusterVersionStates(); } } private void validateHostInDeleteFriendlyState(HostRequest hostRequest, Clusters clusters, boolean forceDelete) throws AmbariException { Set<String> clusterNamesForHost = new HashSet<>(); String hostName = hostRequest.getHostname(); if (null != hostRequest.getClusterName()) { clusterNamesForHost.add(hostRequest.getClusterName()); } else { Set<Cluster> clustersForHost = clusters.getClustersForHost(hostRequest.getHostname()); if (null != clustersForHost) { for (Cluster c : clustersForHost) { clusterNamesForHost.add(c.getClusterName()); } } } for (String clusterName : clusterNamesForHost) { Cluster cluster = clusters.getCluster(clusterName); List<ServiceComponentHost> list = cluster.getServiceComponentHosts(hostName); if (!list.isEmpty()) { List<String> componentsToRemove = new ArrayList<>(); List<String> componentsStarted = new ArrayList<>(); for (ServiceComponentHost sch : list) { componentsToRemove.add(sch.getServiceComponentName()); if (sch.getState() == State.STARTED) { componentsStarted.add(sch.getServiceComponentName()); } } if (forceDelete) { // error if components are running if (!componentsStarted.isEmpty()) { StringBuilder reason = new StringBuilder("Cannot remove host ") .append(hostName) .append(" from ") .append(hostRequest.getClusterName()) .append( ". The following roles exist, and these components must be stopped: "); reason.append(StringUtils.join(componentsToRemove, ", ")); throw new AmbariException(reason.toString()); } } else { if (!componentsToRemove.isEmpty()) { StringBuilder reason = new StringBuilder("Cannot remove host ") .append(hostName) .append(" from ") .append(hostRequest.getClusterName()) .append( ". The following roles exist, and these components must be stopped if running, and then deleted: "); reason.append(StringUtils.join(componentsToRemove, ", ")); throw new AmbariException(reason.toString()); } } } } } /** * Removes hostname from the stateful cluster topology * @param clusters * @param hostRequest * @throws AmbariException */ private void removeHostFromClusterTopology(Clusters clusters, HostRequest hostRequest) throws AmbariException{ if (hostRequest.getClusterName() == null) { for (Cluster c : clusters.getClusters().values()) { removeHostFromClusterTopology(c.getClusterId(), hostRequest.getHostname()); } } else { long clusterId = clusters.getCluster(hostRequest.getClusterName()).getClusterId(); removeHostFromClusterTopology(clusterId, hostRequest.getHostname()); } } private void removeHostFromClusterTopology(long clusterId, String hostname) { ClusterTopology clusterTopology = topologyManager.getClusterTopology(clusterId); if(clusterTopology != null) { clusterTopology.removeHost(hostname); } } /** * Obtain the hostname from the request properties. The hostname property name may differ * depending on the request type. For the low level host resource creation calls, it is always * "Hosts/host_name". For multi host "add host from hostgroup", the hostname property is a top level * property "host_name". * * @param properties request properties * * @return the host name for the host request */ private String getHostNameFromProperties(Map<String, Object> properties) { String hostname = (String) properties.get(HOST_NAME_PROPERTY_ID); return hostname != null ? hostname : (String) properties.get(HOST_NAME_NO_CATEGORY_PROPERTY_ID); } //todo: for api/v1/hosts we also end up here so we need to ensure proper 400 response //todo: since a user shouldn't be posing to that endpoint private RequestStatusResponse submitHostRequests(Request request) throws SystemException { ScaleClusterRequest requestRequest; try { requestRequest = new ScaleClusterRequest(request.getProperties()); } catch (InvalidTopologyTemplateException e) { throw new IllegalArgumentException("Invalid Add Hosts Template: " + e, e); } try { return topologyManager.scaleHosts(requestRequest); } catch (InvalidTopologyException e) { throw new IllegalArgumentException("Topology validation failed: " + e, e); } catch (AmbariException e) { //todo: handle non-system exceptions e.printStackTrace(); //todo: for now just throw SystemException throw new SystemException("Unable to add hosts", e); } } //todo: proper static injection of topology manager public static void setTopologyManager(TopologyManager topologyManager) { HostResourceProvider.topologyManager = topologyManager; } }