blob: 16ea9c2629c7f63b0db4944c98d32d1ec80e7240 [file] [log] [blame]
/*
* 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;
}
}