content: /*
   * 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 java.util.TreeMap;
  
  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.agent.RecoveryConfigHelper;
  import org.apache.ambari.server.agent.stomp.HostLevelParamsHolder;
  import org.apache.ambari.server.agent.stomp.TopologyHolder;
  import org.apache.ambari.server.agent.stomp.dto.HostLevelParamsCluster;
  import org.apache.ambari.server.agent.stomp.dto.TopologyCluster;
  import org.apache.ambari.server.agent.stomp.dto.TopologyHost;
  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.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.events.HostLevelParamsUpdateEvent;
  import org.apache.ambari.server.events.TopologyUpdateEvent;
  import org.apache.ambari.server.events.UpdateEventType;
  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.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.common.collect.ImmutableMap;
  import com.google.common.collect.ImmutableSet;
  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 {
  
    private static final Logger LOG = LoggerFactory.getLogger(HostResourceProvider.class);
  
    // ----- Property ID constants ---------------------------------------------
  
    // Hosts
    public static final String RESPONSE_KEY = "Hosts";
    public static final String ALL_PROPERTIES = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "*";
  
    public static final String CLUSTER_NAME_PROPERTY_ID = "cluster_name";
    public static final String CPU_COUNT_PROPERTY_ID = "cpu_count";
    public static final String DESIRED_CONFIGS_PROPERTY_ID = "desired_configs";
    public static final String DISK_INFO_PROPERTY_ID = "disk_info";
    public static final String HOST_HEALTH_REPORT_PROPERTY_ID = "host_health_report";
    public static final String HOST_NAME_PROPERTY_ID = "host_name";
    public static final String HOST_STATUS_PROPERTY_ID = "host_status";
    public static final String IP_PROPERTY_ID = "ip";
    public static final String LAST_AGENT_ENV_PROPERTY_ID = "last_agent_env";
    public static final String LAST_HEARTBEAT_TIME_PROPERTY_ID = "last_heartbeat_time";
    public static final String LAST_REGISTRATION_TIME_PROPERTY_ID = "last_registration_time";
    public static final String MAINTENANCE_STATE_PROPERTY_ID = "maintenance_state";
    public static final String OS_ARCH_PROPERTY_ID = "os_arch";
    public static final String OS_FAMILY_PROPERTY_ID = "os_family";
    public static final String OS_TYPE_PROPERTY_ID = "os_type";
    public static final String PHYSICAL_CPU_COUNT_PROPERTY_ID = "ph_cpu_count";
    public static final String PUBLIC_NAME_PROPERTY_ID = "public_host_name";
    public static final String RACK_INFO_PROPERTY_ID = "rack_info";
    public static final String RECOVERY_REPORT_PROPERTY_ID = "recovery_report";
    public static final String RECOVERY_SUMMARY_PROPERTY_ID = "recovery_summary";
    public static final String STATE_PROPERTY_ID = "host_state";
    public static final String TOTAL_MEM_PROPERTY_ID = "total_mem";
    public static final String ATTRIBUTES_PROPERTY_ID = "attributes";
  
    public static final String HOST_CLUSTER_NAME_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + CLUSTER_NAME_PROPERTY_ID;
    public static final String HOST_CPU_COUNT_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + CPU_COUNT_PROPERTY_ID;
    public static final String HOST_DESIRED_CONFIGS_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + DESIRED_CONFIGS_PROPERTY_ID;
    public static final String HOST_DISK_INFO_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + DISK_INFO_PROPERTY_ID;
    public static final String HOST_HOST_HEALTH_REPORT_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + HOST_HEALTH_REPORT_PROPERTY_ID;
    public static final String HOST_HOST_STATUS_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + HOST_STATUS_PROPERTY_ID;
    public static final String HOST_IP_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + IP_PROPERTY_ID;
    public static final String HOST_LAST_AGENT_ENV_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + LAST_AGENT_ENV_PROPERTY_ID;
    public static final String HOST_LAST_HEARTBEAT_TIME_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + LAST_HEARTBEAT_TIME_PROPERTY_ID;
    public static final String HOST_LAST_REGISTRATION_TIME_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + LAST_REGISTRATION_TIME_PROPERTY_ID;
    public static final String HOST_MAINTENANCE_STATE_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + MAINTENANCE_STATE_PROPERTY_ID;
    public static final String HOST_HOST_NAME_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + HOST_NAME_PROPERTY_ID;
    public static final String HOST_OS_ARCH_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + OS_ARCH_PROPERTY_ID;
    public static final String HOST_OS_FAMILY_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + OS_FAMILY_PROPERTY_ID;
    public static final String HOST_OS_TYPE_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + OS_TYPE_PROPERTY_ID;
    public static final String HOST_PHYSICAL_CPU_COUNT_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + PHYSICAL_CPU_COUNT_PROPERTY_ID;
    public static final String HOST_PUBLIC_NAME_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + PUBLIC_NAME_PROPERTY_ID;
    public static final String HOST_RACK_INFO_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + RACK_INFO_PROPERTY_ID;
    public static final String HOST_RECOVERY_REPORT_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + RECOVERY_REPORT_PROPERTY_ID;
    public static final String HOST_RECOVERY_SUMMARY_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + RECOVERY_SUMMARY_PROPERTY_ID;
    public static final String HOST_STATE_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + STATE_PROPERTY_ID;
    public static final String HOST_TOTAL_MEM_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + TOTAL_MEM_PROPERTY_ID;
    public static final String HOST_ATTRIBUTES_PROPERTY_ID = PropertyHelper.getPropertyId(RESPONSE_KEY,ATTRIBUTES_PROPERTY_ID);
  
    public static final String BLUEPRINT_PROPERTY_ID = "blueprint";
    public static final String HOST_GROUP_PROPERTY_ID = "host_group";
    public static final String HOST_COUNT_PROPERTY_ID = "host_count";
    public static final String HOST_PREDICATE_PROPERTY_ID = "host_predicate";
  
    //todo use the same json structure for cluster host addition (cluster template and upscale)
  
    /**
     * The key property ids for a Host resource.
     */
    public static final Map<Resource.Type, String> keyPropertyIds = ImmutableMap.<Resource.Type, String>builder()
        .put(Resource.Type.Host, HOST_HOST_NAME_PROPERTY_ID)
        .put(Resource.Type.Cluster, HOST_CLUSTER_NAME_PROPERTY_ID)
        .build();
  
    /**
     * The property ids for a Host resource.
     */
    public static final Set<String> propertyIds = ImmutableSet.of(
        HOST_CLUSTER_NAME_PROPERTY_ID,
        HOST_CPU_COUNT_PROPERTY_ID,
        HOST_DESIRED_CONFIGS_PROPERTY_ID,
        HOST_DISK_INFO_PROPERTY_ID,
        HOST_HOST_HEALTH_REPORT_PROPERTY_ID,
        HOST_HOST_STATUS_PROPERTY_ID,
        HOST_IP_PROPERTY_ID,
        HOST_LAST_AGENT_ENV_PROPERTY_ID,
        HOST_LAST_HEARTBEAT_TIME_PROPERTY_ID,
        HOST_LAST_REGISTRATION_TIME_PROPERTY_ID,
        HOST_MAINTENANCE_STATE_PROPERTY_ID,
        HOST_HOST_NAME_PROPERTY_ID,
        HOST_OS_ARCH_PROPERTY_ID,
        HOST_OS_FAMILY_PROPERTY_ID,
        HOST_OS_TYPE_PROPERTY_ID,
        HOST_PHYSICAL_CPU_COUNT_PROPERTY_ID,
        HOST_PUBLIC_NAME_PROPERTY_ID,
        HOST_RACK_INFO_PROPERTY_ID,
        HOST_RECOVERY_REPORT_PROPERTY_ID,
        HOST_RECOVERY_SUMMARY_PROPERTY_ID,
        HOST_STATE_PROPERTY_ID,
        HOST_TOTAL_MEM_PROPERTY_ID,
        HOST_ATTRIBUTES_PROPERTY_ID);
  
    @Inject
    private OsFamily osFamily;
  
    @Inject
    private static TopologyManager topologyManager;
  
    @Inject
    private TopologyHolder topologyHolder;
  
    @Inject
    private HostLevelParamsHolder hostLevelParamsHolder;
  
    @Inject
    private RecoveryConfigHelper recoveryConfigHelper;
  
  
    // ----- Constructors ----------------------------------------------------
  
    /**
     * Create a  new resource provider for the given management controller.
     *
     * @param managementController  the management controller
     */
    @AssistedInject
    HostResourceProvider(@Assisted AmbariManagementController managementController) {
      super(Resource.Type.Host, 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((Command<Void>) () -> {
          createHosts(request);
          return null;
        });
      }
      notifyCreate(Resource.Type.Host, request);
  
      return getRequestStatus(createResponse);
    }
  
    @Override
    protected Set<Resource> getResourcesAuthorized(Request request, Predicate predicate)
        throws SystemException, 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(() -> 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_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,
            response.getCpuCount(), requestedIds);
        setResourceProperty(resource, HOST_PHYSICAL_CPU_COUNT_PROPERTY_ID,
            response.getPhCpuCount(), requestedIds);
        setResourceProperty(resource, HOST_OS_ARCH_PROPERTY_ID,
            response.getOsArch(), requestedIds);
        setResourceProperty(resource, HOST_OS_TYPE_PROPERTY_ID,
            response.getOsType(), requestedIds);
        setResourceProperty(resource, HOST_OS_FAMILY_PROPERTY_ID,
            response.getOsFamily(), 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.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((Command<Void>) () -> {
        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<>();
  
      for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
        requests.add(getRequest(propertyMap));
      }
  
      DeleteStatusMetaData deleteStatusMetaData = modifyResources(() -> deleteHosts(requests, request.isDryRunRequest()));
  
      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(HOST_GROUP_PROPERTY_ID);
      baseUnsupported.remove(HOST_NAME_PROPERTY_ID);
      baseUnsupported.remove(HOST_COUNT_PROPERTY_ID);
      baseUnsupported.remove(HOST_PREDICATE_PROPERTY_ID);
      baseUnsupported.remove(RACK_INFO_PROPERTY_ID);
  
      return checkConfigPropertyIds(baseUnsupported, "Hosts");
    }
  
  
    // ----- AbstractResourceProvider ------------------------------------------
  
    @Override
    protected Set<String> getPKPropertyIds() {
      return new HashSet<>(keyPropertyIds.values());
    }
  
  
    // ----- 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(HOST_GROUP_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);
      }
  
      HostRequest hostRequest = new HostRequest(
          getHostNameFromProperties(properties),
          (String) properties.get(HOST_CLUSTER_NAME_PROPERTY_ID)
      );
      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(RACK_INFO_PROPERTY_ID));
  
      hostRequest.setRackInfo(rackInfo);
      hostRequest.setBlueprintName((String) properties.get(BLUEPRINT_PROPERTY_ID));
      hostRequest.setHostGroupName((String) properties.get(HOST_GROUP_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.
     */
    public synchronized void createHosts(Request request)
        throws AmbariException, AuthorizationException {
  
      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(HOST_GROUP_PROPERTY_ID)) {
          createHostResource(clusters, duplicates, unknowns, allHosts, hostRequest);
        }
      }
  
      if (!duplicates.isEmpty()) {
        throw new IllegalArgumentException("Invalid request contains duplicate hostnames"
                + ", hostnames=" + String.join(",", duplicates));
      }
  
      if (!unknowns.isEmpty()) {
        throw new IllegalArgumentException("Attempted to add unknown hosts to a cluster.  " +
                "These hosts have not been registered with the server: " +
                String.join(",", unknowns));
      }
  
      Map<String, Set<String>> hostClustersMap = new HashMap<>();
      Map<String, Map<String, String>> hostAttributes = new HashMap<>();
      Set<String> allClusterSet = new HashSet<>();
  
      TreeMap<String, TopologyCluster> addedTopologies = new TreeMap<>();
      List<HostLevelParamsUpdateEvent> hostLevelParamsUpdateEvents = new ArrayList<>();
      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);
          Cluster cl = clusters.getCluster(hostRequest.getClusterName());
          String clusterId = Long.toString(cl.getClusterId());
          if (!addedTopologies.containsKey(clusterId)) {
            addedTopologies.put(clusterId, new TopologyCluster());
          }
          Host addedHost = clusters.getHost(hostRequest.getHostname());
          addedTopologies.get(clusterId).addTopologyHost(new TopologyHost(addedHost.getHostId(), addedHost.getHostName(),
              addedHost.getRackInfo(), addedHost.getIPv4()));
          HostLevelParamsUpdateEvent hostLevelParamsUpdateEvent = new HostLevelParamsUpdateEvent(addedHost.getHostId(),
              clusterId,
              new HostLevelParamsCluster( recoveryConfigHelper.getRecoveryConfig(cl.getClusterName(), addedHost.getHostName()),
              getManagementController().getBlueprintProvisioningStates(cl.getClusterId(), addedHost.getHostId())
          ));
          hostLevelParamsUpdateEvents.add(hostLevelParamsUpdateEvent);
        }
      }
      clusters.updateHostWithClusterAndAttributes(hostClustersMap, hostAttributes);
  
      // TODO add rack change to topology update
      updateHostRackInfoIfChanged(clusters, hostRequests);
  
      for (HostLevelParamsUpdateEvent hostLevelParamsUpdateEvent : hostLevelParamsUpdateEvents) {
        hostLevelParamsHolder.updateData(hostLevelParamsUpdateEvent);
      }
      TopologyUpdateEvent topologyUpdateEvent =
          new TopologyUpdateEvent(addedTopologies, UpdateEventType.UPDATE);
      topologyHolder.updateData(topologyUpdateEvent);
    }
  
    /**
     * Iterates through the provided host request and checks if there is rack info provided.
     * If the rack info differs from the rack info of the host than updates it with the value from
     * the host request.
     * @param clusters
     * @param hostRequests
     * @throws AmbariException
     * @throws AuthorizationException
     */
    private void updateHostRackInfoIfChanged(Clusters clusters, Set<HostRequest> hostRequests)
      throws AmbariException, AuthorizationException {
  
      HashSet<String> rackChangeAffectedClusters = new HashSet<>();
  
      for (HostRequest hostRequest : hostRequests) {
        String clusterName = hostRequest.getClusterName();
  
        if (StringUtils.isNotBlank(clusterName)) {
          Cluster cluster = clusters.getCluster(clusterName);
          Host host = clusters.getHost(hostRequest.getHostname());
  
          if (updateHostRackInfoIfChanged(cluster, host, hostRequest))
            rackChangeAffectedClusters.add(clusterName);
        }
      }
      // TODO rack change topology update
      for (String clusterName: rackChangeAffectedClusters) {
        getManagementController().registerRackChange(clusterName);
      }
    }
  
  
  
    /**
     * If the rack info provided in the request differs from the rack info of the host
     * update the rack info of the host with the value from the host request
     *
     * @param cluster The cluster to check user privileges against. User is required
     *                to have {@link RoleAuthorization#HOST_ADD_DELETE_HOSTS} rights on the cluster.
     * @param host The host of which rack information is to be updated
     * @param hostRequest
     * @return true is host was updated otherwise false
     * @throws AmbariException
     * @throws AuthorizationException
     */
    private boolean updateHostRackInfoIfChanged(Cluster cluster, Host host, HostRequest hostRequest)
      throws AmbariException, AuthorizationException {
  
      Long resourceId = cluster.getResourceId();
  
      String hostRackInfo = host.getRackInfo();
      String requestRackInfo = hostRequest.getRackInfo();
  
      boolean rackChange = requestRackInfo != null && !requestRackInfo.equals(hostRackInfo);
  
      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");
        }
        //TODO topology update
  
        host.setRackInfo(requestRackInfo);
      }
  
      return rackChange;
    }
  
    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={}", request.getHostname(), 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,
                                         boolean useClusterHostInfo)
        throws SystemException,
        NoSuchParentResourceException,
        UnsupportedPropertyException {
  
  
      return ((HostComponentResourceProvider) getResourceProvider(Resource.Type.HostComponent)).
          install(cluster, hostname, skipInstallForComponents, dontSkipInstallForComponents, skipFailure, useClusterHostInfo);
    }
  
    public RequestStatusResponse start(final String cluster, final String hostname)
        throws 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, osFamily));
        } 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;
    }
  
    /**
     * @param osFamily provides OS to OS family lookup; may be null if OS family is ignored anyway (eg. for liveness check)
     */
    protected static Set<HostResponse> getHosts(AmbariManagementController controller, HostRequest request, OsFamily osFamily)
        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()));
            if (osFamily != null) {
              String hostOsFamily = osFamily.find(r.getOsType());
              if (hostOsFamily == null) {
                LOG.error("Can not find host OS family. For OS type = '{}' and host name = '{}'", r.getOsType(), r.getHostname());
              }
              r.setOsFamily(hostOsFamily);
            }
  
            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");
        }
      }
  
      TreeMap<String, TopologyCluster> topologyUpdates = new TreeMap<>();
      for (HostRequest request : requests) {
        if (LOG.isDebugEnabled()) {
          LOG.debug("Received an updateHost request, hostname={}, request={}", request.getHostname(), request);
        }
  
        Host host = clusters.getHost(request.getHostname());
  
        String clusterName = request.getClusterName();
        Cluster cluster = clusters.getCluster(clusterName);
        Long clusterId = cluster.getClusterId();
        Long resourceId = cluster.getResourceId();
        TopologyHost topologyHost = new TopologyHost(host.getHostId(), host.getHostName());
  
        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
        }
  
        boolean rackChange = updateHostRackInfoIfChanged(cluster, host, request);
  
        if (rackChange) {
          topologyHost.setRackName(host.getRackInfo());
        }
  
        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());
          topologyHost.setHostName(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("(configchange) cluster '" + cluster.getClusterName() + "', "
                      + "host '" + host.getHostName() + "' "
                      + "changed by: '" + authName + "'; "
                      + "type='" + baseConfig.getType() + "' "
                      + "version='" + baseConfig.getVersion() + "'"
                      + "tag='" + baseConfig.getTag() + "'"
                      + (null == oldConfig ? "" : ", from='" + oldConfig.getTag() + "'"));
                }
              }
            }
          }
        }
  
        if (StringUtils.isNotBlank(clusterName) && rackChange) {
          // Authorization check for this update was performed before we got to this point.
          controller.registerRackChange(clusterName);
        }
  
        if (!topologyUpdates.containsKey(clusterId.toString())) {
          topologyUpdates.put(clusterId.toString(), new TopologyCluster());
        }
        topologyUpdates.get(clusterId.toString()).addTopologyHost(topologyHost);
        TopologyUpdateEvent topologyUpdateEvent = new TopologyUpdateEvent(topologyUpdates,
            UpdateEventType.UPDATE);
        topologyHolder.updateData(topologyUpdateEvent);
        //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)
        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);
          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<Long> hostIds = new HashSet<>();
      TreeMap<String, TopologyCluster> topologyUpdates = new TreeMap<>();
      for (HostRequest hostRequest : requests) {
        // Assume the user also wants to delete it entirely, including all clusters.
        String hostname = hostRequest.getHostname();
        Long hostId = clusters.getHost(hostname).getHostId();
        hostNames.add(hostname);
        hostIds.add(hostId);
  
        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 {
          Set<Cluster> hostClusters = new HashSet<>(clusters.getClustersForHost(hostname));
          clusters.deleteHost(hostname);
          for (Cluster cluster : hostClusters) {
            String clusterId = Long.toString(cluster.getClusterId());
            if (!topologyUpdates.containsKey(clusterId)) {
              topologyUpdates.put(clusterId, new TopologyCluster());
            }
            topologyUpdates.get(clusterId).getTopologyHosts().add(new TopologyHost(hostId, hostname));
          }
          deleteStatusMetaData.addDeletedKey(hostname);
        } catch (Exception ex) {
          deleteStatusMetaData.addException(hostname, ex);
        }
        removeHostFromClusterTopology(clusters, hostRequest);
        for (LogicalRequest logicalRequest: topologyManager.getRequests(Collections.emptyList())) {
          logicalRequest.removeHostRequestByHostName(hostname);
        }
      }
      clusters.publishHostsDeletion(hostIds, hostNames);
      TopologyUpdateEvent topologyUpdateEvent = new TopologyUpdateEvent(topologyUpdates,
          UpdateEventType.DELETE);
      topologyHolder.updateData(topologyUpdateEvent);
    }
  
    private void validateHostInDeleteFriendlyState(HostRequest hostRequest, Clusters clusters) 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> componentsStarted = new ArrayList<>();
          for (ServiceComponentHost sch : list) {
            if (!sch.canBeRemoved()) {
              componentsStarted.add(sch.getServiceComponentName());
            }
          }
  
          // 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 are not in the removable state: ");
  
            reason.append(StringUtils.join(componentsStarted, ", "));
  
            throw new AmbariException(reason.toString());
          }
        }
      }
    }
  
    /**
     * Removes hostname from the stateful cluster topology
     */
    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
     */
    public static String getHostNameFromProperties(Map<String, Object> properties) {
      String hostname = (String) properties.get(HOST_HOST_NAME_PROPERTY_ID);
  
      return hostname != null ? hostname :
          (String) properties.get(HOST_NAME_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;
    }
  }
  