/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ambari.server.controller.internal; import java.util.Collections; import java.util.Map; import java.util.Set; import org.apache.ambari.server.api.predicate.InvalidQueryException; import org.apache.ambari.server.stack.NoSuchStackException; import org.apache.ambari.server.topology.Blueprint; import org.apache.ambari.server.topology.Configuration; import org.apache.ambari.server.topology.HostGroupInfo; import org.apache.ambari.server.topology.InvalidTopologyTemplateException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A request for a scaling an existing cluster. */ public class ScaleClusterRequest extends BaseClusterRequest { private static final Logger LOGGER = LoggerFactory.getLogger(ScaleClusterRequest.class); /** * cluster name */ private String clusterName; /** * Constructor. * * @param propertySet set of request properties * * @throws InvalidTopologyTemplateException if any validation of properties fails */ public ScaleClusterRequest(Set<Map<String, Object>> propertySet) throws InvalidTopologyTemplateException { for (Map<String, Object> properties : propertySet) { // can only operate on a single cluster per logical request if (getClusterName() == null) { setClusterName(String.valueOf(properties.get(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID))); } // currently don't allow cluster scoped configuration in scaling operation setConfiguration(new Configuration(Collections.<String, Map<String, String>>emptyMap(), Collections.<String, Map<String, Map<String, String>>>emptyMap())); parseHostGroups(properties); } } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } @Override public Long getClusterId() { return clusterId; } public void setClusterId(Long clusterId) { this.clusterId = clusterId; } @Override public Type getType() { return Type.SCALE; } @Override public String getDescription() { return String.format("Scale Cluster '%s' (+%s hosts)", clusterName, getTotalRequestedHostCount()); } /** * Parse and set host group information. * * @param properties request properties * @throws InvalidTopologyTemplateException if any property validation fails */ //todo: need to use fully qualified host group name. For now, disregard name collisions across BP's private void parseHostGroups(Map<String, Object> properties) throws InvalidTopologyTemplateException { String blueprintName = String.valueOf(properties.get(HostResourceProvider.BLUEPRINT_PROPERTY_ID)); if (blueprintName == null || blueprintName.equals("null")) { throw new InvalidTopologyTemplateException("Blueprint name must be specified for all host groups"); } String hgName = String.valueOf(properties.get(HostResourceProvider.HOSTGROUP_PROPERTY_ID)); if (hgName == null || hgName.equals("null")) { throw new InvalidTopologyTemplateException("A name must be specified for all host groups"); } Blueprint blueprint = getBlueprint(); if (getBlueprint() == null) { blueprint = parseBlueprint(blueprintName); setBlueprint(blueprint); } else if (! blueprintName.equals(blueprint.getName())) { throw new InvalidTopologyTemplateException( "Currently, a scaling request may only refer to a single blueprint"); } String hostName = getHostNameFromProperties(properties); boolean containsHostCount = properties.containsKey(HostResourceProvider.HOST_COUNT_PROPERTY_ID); boolean containsHostPredicate = properties.containsKey(HostResourceProvider.HOST_PREDICATE_PROPERTY_ID); if (hostName != null && (containsHostCount || containsHostPredicate)) { throw new InvalidTopologyTemplateException( "Can't specify host_count or host_predicate if host_name is specified in hostgroup: " + hgName); } if (hostName == null && ! containsHostCount) { throw new InvalidTopologyTemplateException( "Must specify either host_name or host_count for hostgroup: " + hgName); } HostGroupInfo hostGroupInfo = getHostGroupInfo().get(hgName); if (hostGroupInfo == null) { if (blueprint.getHostGroup(hgName) == null) { throw new InvalidTopologyTemplateException("Invalid host group specified in request: " + hgName); } hostGroupInfo = new HostGroupInfo(hgName); getHostGroupInfo().put(hgName, hostGroupInfo); } // specifying configuration is scaling request isn't permitted hostGroupInfo.setConfiguration(new Configuration(Collections.<String, Map<String, String>>emptyMap(), Collections.<String, Map<String, Map<String, String>>>emptyMap())); // process host_name and host_count if (containsHostCount) { //todo: host_count and host_predicate up one level if (containsHostPredicate) { String predicate = String.valueOf(properties.get(HostResourceProvider.HOST_PREDICATE_PROPERTY_ID)); validateHostPredicateProperties(predicate); try { hostGroupInfo.setPredicate(predicate); } catch (InvalidQueryException e) { throw new InvalidTopologyTemplateException( String.format("Unable to compile host predicate '%s': %s", predicate, e), e); } } if (! hostGroupInfo.getHostNames().isEmpty()) { throw new InvalidTopologyTemplateException( "Can't specify both host_name and host_count for the same hostgroup: " + hgName); } hostGroupInfo.setRequestedCount(Integer.valueOf(String.valueOf( properties.get(HostResourceProvider.HOST_COUNT_PROPERTY_ID)))); } else { if (hostGroupInfo.getRequestedHostCount() != hostGroupInfo.getHostNames().size()) { // host_name specified in one host block and host_count in another for the same group throw new InvalidTopologyTemplateException("Invalid host group specified in request: " + hgName); } hostGroupInfo.addHost(hostName); hostGroupInfo.addHostRackInfo(hostName, processRackInfo(properties)); } } private String processRackInfo(Map<String, Object> properties) { String rackInfo = null; if (properties.containsKey(HostResourceProvider.HOST_RACK_INFO_PROPERTY_ID)) { rackInfo = (String) properties.get(HostResourceProvider.HOST_RACK_INFO_PROPERTY_ID); } else if (properties.containsKey(HostResourceProvider.HOST_RACK_INFO_NO_CATEGORY_PROPERTY_ID)) { rackInfo = (String) properties.get(HostResourceProvider.HOST_RACK_INFO_NO_CATEGORY_PROPERTY_ID); } else { LOGGER.debug("No rack info provided"); } return rackInfo; } /** * Parse blueprint. * * @param blueprintName blueprint name * @return blueprint instance * * @throws InvalidTopologyTemplateException if specified blueprint or stack doesn't exist */ private Blueprint parseBlueprint(String blueprintName) throws InvalidTopologyTemplateException { Blueprint blueprint; try { blueprint = getBlueprintFactory().getBlueprint(blueprintName); } catch (NoSuchStackException e) { throw new InvalidTopologyTemplateException("Invalid stack specified in the blueprint: " + blueprintName); } if (blueprint == null) { throw new InvalidTopologyTemplateException("The specified blueprint doesn't exist: " + blueprintName); } return blueprint; } /** * Get the host name from the request properties. * * @param properties request properties * @return host name */ //todo: this was copied exactly from HostResourceProvider private String getHostNameFromProperties(Map<String, Object> properties) { String hostName = (String) properties.get(HostResourceProvider.HOST_NAME_PROPERTY_ID); if (hostName == null) { hostName = (String) properties.get(HostResourceProvider.HOST_NAME_NO_CATEGORY_PROPERTY_ID); } return hostName; } /** * Get the total number of requested hosts for the request. * @return total requested host count */ private int getTotalRequestedHostCount() { int count = 0; for (HostGroupInfo groupInfo : getHostGroupInfo().values()) { count += groupInfo.getRequestedHostCount(); } return count; } }