| /** |
| * 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; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.ambari.server.AmbariException; |
| import org.apache.ambari.server.HostNotFoundException; |
| import org.apache.ambari.server.controller.RootServiceResponseFactory.Services; |
| import org.apache.ambari.server.controller.internal.RequestOperationLevel; |
| import org.apache.ambari.server.controller.internal.RequestResourceFilter; |
| import org.apache.ambari.server.controller.spi.Resource; |
| import org.apache.ambari.server.state.Alert; |
| import org.apache.ambari.server.state.Cluster; |
| import org.apache.ambari.server.state.Clusters; |
| import org.apache.ambari.server.state.Host; |
| import org.apache.ambari.server.state.MaintenanceState; |
| import org.apache.ambari.server.state.Service; |
| import org.apache.ambari.server.state.ServiceComponent; |
| import org.apache.ambari.server.state.ServiceComponentHost; |
| import org.apache.commons.lang.StringUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.inject.Inject; |
| import com.google.inject.Injector; |
| import com.google.inject.Singleton; |
| |
| /** |
| * Used to help manage maintenance state checks. |
| */ |
| @Singleton |
| public class MaintenanceStateHelper { |
| private static final Logger LOG = LoggerFactory.getLogger(MaintenanceStateHelper.class); |
| |
| @Inject |
| private Clusters clusters; |
| |
| @Inject |
| public MaintenanceStateHelper(Injector injector) { |
| injector.injectMembers(this); |
| } |
| |
| /** |
| * @param cluster |
| * cluster for request |
| * @param levelObj |
| * operation level (can be null) |
| * @param reqFilter |
| * request resource filter for operation (can't be null) |
| * @param serviceName |
| * service name (can be null) |
| * @param componentName |
| * component name (can be null) |
| * @param hostname |
| * host name (can't be null) |
| * @return true if operation may be performed on a given target (based on |
| * target's maintenance status). |
| */ |
| public boolean isOperationAllowed(Cluster cluster, |
| RequestOperationLevel levelObj, RequestResourceFilter reqFilter, |
| String serviceName, String componentName, String hostname) |
| throws AmbariException { |
| Resource.Type level; |
| if (levelObj == null) { |
| level = guessOperationLevel(reqFilter); |
| } else { |
| level = levelObj.getLevel(); |
| } |
| return isOperationAllowed(cluster, level, serviceName, componentName, |
| hostname); |
| } |
| |
| /** |
| * @param cluster |
| * cluster for request |
| * @param level |
| * operation level (can't be null) |
| * @param serviceName |
| * service name (can be null) |
| * @param componentName |
| * component name (can be null) |
| * @param hostname |
| * host name (can't be null) |
| * @return true if operation may be performed on a given target (based on |
| * target's maintenance status). |
| */ |
| boolean isOperationAllowed(Cluster cluster, Resource.Type level, |
| String serviceName, String componentName, String hostname) |
| throws AmbariException { |
| if (serviceName != null && !serviceName.isEmpty()) { |
| Service service = cluster.getService(serviceName); |
| if (componentName != null && !componentName.isEmpty()) { |
| ServiceComponentHost sch = service.getServiceComponent(componentName).getServiceComponentHost( |
| hostname); |
| return isOperationAllowed(level, sch); |
| } else { // Only service name is defined |
| return isOperationAllowed(level, service); |
| } |
| } else { // Service is not defined, using host |
| Host host = clusters.getHost(hostname); |
| return isOperationAllowed(host, cluster.getClusterId(), level); |
| } |
| } |
| |
| /** |
| * @param level |
| * operation level |
| * @param service |
| * the service |
| * @return true if operation may be performed on a given target (based on |
| * target's maintenance status). |
| */ |
| public boolean isOperationAllowed(Resource.Type level, Service service) |
| throws AmbariException { |
| if (level == Resource.Type.Cluster) { |
| return service.getMaintenanceState() == MaintenanceState.OFF; |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * @param host |
| * the service |
| * @param clusterId |
| * cluster the host belongs to |
| * @param level |
| * operation level for request |
| * @return true if operation may be performed on a given target (based on |
| * target's maintenance status). |
| */ |
| public boolean isOperationAllowed(Host host, long clusterId, |
| Resource.Type level) throws AmbariException { |
| if (level == Resource.Type.Cluster) { |
| return host.getMaintenanceState(clusterId) == MaintenanceState.OFF; |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * Get effective state of HostComponent |
| * |
| * @param sch |
| * the host component |
| * @param host |
| * the host |
| * @return the maintenance state |
| * @throws AmbariException |
| */ |
| public MaintenanceState getEffectiveState(ServiceComponentHost sch, Host host) |
| throws AmbariException { |
| Cluster cluster = clusters.getCluster(sch.getClusterName()); |
| Service service = cluster.getService(sch.getServiceName()); |
| |
| if (null == host) { |
| throw new HostNotFoundException(cluster.getClusterName(), |
| sch.getHostName()); |
| } |
| |
| return getEffectiveState(cluster.getClusterId(), service, host, sch); |
| } |
| |
| /** |
| * Gets the effective state for a HostComponent |
| * |
| * @param sch |
| * the host component |
| * @return the maintenance state |
| * @throws AmbariException |
| */ |
| public MaintenanceState getEffectiveState(ServiceComponentHost sch) |
| throws AmbariException { |
| Host host = clusters.getHost(sch.getHostName()); |
| return getEffectiveState(sch, host); |
| } |
| |
| /** |
| * @param clusterId |
| * the cluster id |
| * @param service |
| * the service |
| * @param host |
| * the host |
| * @param sch |
| * the host component |
| * @return the effective maintenance state |
| */ |
| private MaintenanceState getEffectiveState(long clusterId, Service service, |
| Host host, ServiceComponentHost sch) { |
| |
| MaintenanceState schState = sch.getMaintenanceState(); |
| if (MaintenanceState.ON == schState) { |
| return MaintenanceState.ON; |
| } |
| |
| MaintenanceState serviceState = service.getMaintenanceState(); |
| MaintenanceState hostState = host.getMaintenanceState(clusterId); |
| |
| if (MaintenanceState.OFF != serviceState |
| && MaintenanceState.OFF != hostState) { |
| return MaintenanceState.IMPLIED_FROM_SERVICE_AND_HOST; |
| } |
| |
| if (MaintenanceState.OFF != serviceState) { |
| return MaintenanceState.IMPLIED_FROM_SERVICE; |
| } |
| |
| if (MaintenanceState.OFF != hostState) { |
| return MaintenanceState.IMPLIED_FROM_HOST; |
| } |
| |
| return schState; |
| } |
| |
| /** |
| * Gets the effective maintenance state for a given alert. This will determine |
| * whether the alert needs to report that it is in maintenance mode. |
| * |
| * @param clusterId |
| * the ID of the cluster for the alert. |
| * @param alert |
| * the alert (not {@code null}). |
| * @return {@link MaintenanceState#OFF} if the host/service/component all |
| * report they are not in maintenance mode; |
| * {@link MaintenanceState#ON} otherwise. |
| * @throws AmbariException |
| */ |
| public MaintenanceState getEffectiveState(long clusterId, Alert alert) throws AmbariException { |
| String serviceName = alert.getService(); |
| String componentName = alert.getComponent(); |
| String hostName = alert.getHostName(); |
| |
| if (null == serviceName && null == hostName) { |
| LOG.warn("Unable to determine maintenance state for an alert without a service or host"); |
| return MaintenanceState.OFF; |
| } |
| |
| // always start with the host first; we always have a host unless this is an |
| // aggregate alert (service-level alert) |
| if (StringUtils.isNotBlank(hostName)) { |
| Host host = clusters.getHost(hostName); |
| if (host.getMaintenanceState(clusterId) != MaintenanceState.OFF) { |
| return MaintenanceState.ON; |
| } |
| } |
| |
| // the AMBARI service is not a real service; it's never in MM |
| if( StringUtils.equals(Services.AMBARI.name(), serviceName)){ |
| return MaintenanceState.OFF; |
| } |
| |
| // the host is not in MM, move onto the service |
| Cluster cluster = clusters.getClusterById(clusterId); |
| Service service = cluster.getService(serviceName); |
| if( service.getMaintenanceState() != MaintenanceState.OFF ){ |
| return MaintenanceState.ON; |
| } |
| |
| if( StringUtils.isNotBlank(componentName)){ |
| ServiceComponent serviceComponent = service.getServiceComponent(componentName); |
| ServiceComponentHost serviceComponentHost = serviceComponent.getServiceComponentHost(hostName); |
| |
| if (serviceComponentHost.getMaintenanceState() != MaintenanceState.OFF) { |
| return MaintenanceState.ON; |
| } |
| } |
| |
| // MM is off at the host, service, and component level; return OFF |
| return MaintenanceState.OFF; |
| } |
| |
| /** |
| * @param clusters |
| * the collection of clusters |
| * @param cluster |
| * the specific cluster to check |
| * @return a property map of all host components that are in a maintenance |
| * state (either {@link MaintenanceState#ON} or |
| * {@link MaintenanceState#IMPLIED_FROM_HOST} or |
| * {@link MaintenanceState#IMPLIED_FROM_SERVICE} or |
| * {@link MaintenanceState#IMPLIED_FROM_SERVICE_AND_HOST}) |
| */ |
| public Set<Map<String, String>> getMaintenanceHostComponents( |
| Clusters clusters, Cluster cluster) throws AmbariException { |
| Set<Map<String, String>> set = new HashSet<Map<String, String>>(); |
| |
| Map<String, Host> hosts = clusters.getHostsForCluster(cluster.getClusterName()); |
| |
| for (Service service : cluster.getServices().values()) { |
| for (ServiceComponent sc : service.getServiceComponents().values()) { |
| if (sc.isClientComponent()) { |
| continue; |
| } |
| |
| for (ServiceComponentHost sch : sc.getServiceComponentHosts().values()) { |
| Host host = hosts.get(sch.getHostName()); |
| |
| if (MaintenanceState.OFF != getEffectiveState(cluster.getClusterId(), |
| service, host, sch)) { |
| Map<String, String> map = new HashMap<String, String>(); |
| map.put("host", sch.getHostName()); |
| map.put("service", sch.getServiceName()); |
| map.put("component", sch.getServiceComponentName()); |
| set.add(map); |
| } |
| } |
| } |
| } |
| |
| return set; |
| } |
| |
| /** |
| * Determine based on the requesting Resource level and the state of the |
| * operand whether to allow operations on it. |
| * |
| * @param operationLevel |
| * Request Source: {CLUSTER, SERVICE, HOSTCOMPONENT, HOST} |
| * @param sch |
| * HostComponent which is the operand of the operation |
| * @return |
| * @throws AmbariException |
| */ |
| public boolean isOperationAllowed(Resource.Type operationLevel, |
| ServiceComponentHost sch) throws AmbariException { |
| MaintenanceState maintenanceState = getEffectiveState(sch); |
| |
| switch (operationLevel.getInternalType()) { |
| case Cluster: |
| if (maintenanceState.equals(MaintenanceState.OFF)) { |
| return true; |
| } |
| break; |
| case Service: |
| if (maintenanceState.equals(MaintenanceState.IMPLIED_FROM_SERVICE) |
| || maintenanceState.equals(MaintenanceState.OFF)) { |
| return true; |
| } |
| break; |
| case Host: |
| if (maintenanceState.equals(MaintenanceState.IMPLIED_FROM_HOST) |
| || maintenanceState.equals(MaintenanceState.OFF)) { |
| return true; |
| } |
| break; |
| case HostComponent: { |
| return true; |
| } |
| default: |
| LOG.warn("Unsupported Resource type, type = " + operationLevel); |
| break; |
| } |
| return false; |
| } |
| |
| /** |
| * Fallback logic for guessing operation level from request resource filter. |
| * It is used when operation level is not defined explicitly. |
| */ |
| public Resource.Type guessOperationLevel(RequestResourceFilter filter) { |
| Resource.Type result; |
| if (filter == null) { |
| // CLUSTER can be assumed to be the default level if no ResourceFilter |
| // is specified, because for any other resource operation |
| // a ResourceFilter is mandatory. |
| result = Resource.Type.Cluster; |
| } else { |
| boolean serviceDefined = filter.getServiceName() != null; |
| boolean componentDefined = filter.getComponentName() != null; |
| boolean hostsDefined = filter.getHostNames() != null |
| && filter.getHostNames().size() > 0; |
| |
| if (hostsDefined & componentDefined) { |
| result = Resource.Type.HostComponent; |
| } else if (!serviceDefined & hostsDefined) { |
| result = Resource.Type.Host; |
| } else if (serviceDefined & !hostsDefined) { |
| // This option also includes resource filters |
| // that target service on few hosts |
| result = Resource.Type.Service; |
| } else { // Absolute fallback |
| // Cluster level should be a good option for any unsure cases |
| // Cluster-level actions only |
| result = Resource.Type.Cluster; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Removes from a set all hosts that match a given condition. |
| * |
| * @param candidateHosts |
| * source set that should to be modified |
| * @param condition |
| * condition |
| * @return all hosts that have been removed from a candidateHosts |
| */ |
| public Set<String> filterHostsInMaintenanceState(Set<String> candidateHosts, |
| HostPredicate condition) throws AmbariException { |
| // Filter hosts that are in MS |
| Set<String> removedHosts = new HashSet<String>(); |
| for (String hostname : candidateHosts) { |
| if (condition.shouldHostBeRemoved(hostname)) { |
| removedHosts.add(hostname); |
| } |
| } |
| candidateHosts.removeAll(removedHosts); |
| return removedHosts; |
| } |
| |
| /** |
| * The {@link HostPredicate} interaface is used as a way to pass a custom |
| * implementation into one of the many |
| * {@link MaintenanceStateHelper#isOperationAllowed(org.apache.ambari.server.controller.spi.Resource.Type, Service)} |
| * methods. |
| */ |
| public static interface HostPredicate { |
| /** |
| * Gets whether the specified host should not be included in a result set. |
| * |
| * @param hostname |
| * the host (not {@code null}). |
| * @return {@code true} to indicate that the specified host is not a valid |
| * choice, {@code false} otherwise. |
| * @throws AmbariException |
| */ |
| public boolean shouldHostBeRemoved(String hostname) throws AmbariException; |
| } |
| } |