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.checks;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.ambari.annotations.UpgradeCheckInfo;
  import org.apache.ambari.server.AmbariException;
  import org.apache.ambari.server.state.Cluster;
  import org.apache.ambari.spi.upgrade.UpgradeCheckDescription;
  import org.apache.ambari.spi.upgrade.UpgradeCheckGroup;
  import org.apache.ambari.spi.upgrade.UpgradeCheckRequest;
  import org.apache.ambari.spi.upgrade.UpgradeCheckResult;
  import org.apache.ambari.spi.upgrade.UpgradeCheckStatus;
  import org.apache.ambari.spi.upgrade.UpgradeCheckType;
  import org.apache.commons.lang.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.google.common.collect.ImmutableMap;
  import com.google.inject.Singleton;
  
  /**
   * Checks if Atlas service is present. Upgrade to stack HDP 2.5 can't pursuit
   * with existed on the cluster Atlas service.
   */
  @Singleton
  @UpgradeCheckInfo(group = UpgradeCheckGroup.DEFAULT)
  public class ServicePresenceCheck extends ClusterCheck{
  
    private static final Logger LOG = LoggerFactory.getLogger(ServicePresenceCheck.class);
  
    static final String KEY_SERVICE_REPLACED = "service_replaced";
    static final String KEY_SERVICE_REMOVED = "service_removed";
  
    /*
     * List of services that do not support upgrade
     * services must be removed before the stack upgrade
     * */
    static final String NO_UPGRADE_SUPPORT_SERVICES_PROPERTY_NAME = "no-upgrade-support-service-names";
  
    /*
     * List of services removed from the new release
     * */
    static final String REMOVED_SERVICES_PROPERTY_NAME = "removed-service-names";
  
    /*
     * List of services replaced by other services in the new release
     * */
    static final String REPLACED_SERVICES_PROPERTY_NAME = "replaced-service-names";
  
    /*
     * Such as Spark to Spark2
     */
    static final String NEW_SERVICES_PROPERTY_NAME = "new-service-names";
  
    static final UpgradeCheckDescription SERVICE_PRESENCE_CHECK = new UpgradeCheckDescription("SERVICE_PRESENCE_CHECK",
        UpgradeCheckType.SERVICE,
        "Service Is Not Supported For Upgrades",
        new ImmutableMap.Builder<String, String>()
          .put(UpgradeCheckDescription.DEFAULT,
              "The %s service is currently installed on the cluster. " +
              "This service does not support upgrades and must be removed before the upgrade can continue. " +
              "After upgrading, %s can be reinstalled")
          .put(ServicePresenceCheck.KEY_SERVICE_REMOVED,
              "The %s service is currently installed on the cluster. " +
              "This service is removed from the new release and must be removed before the upgrade can continue.").build());
  
  
    public ServicePresenceCheck(){
      super(SERVICE_PRESENCE_CHECK);
    }
  
    @Override
    public UpgradeCheckResult perform(UpgradeCheckRequest request) throws AmbariException {
      UpgradeCheckResult result = new UpgradeCheckResult(this);
  
      final Cluster cluster = clustersProvider.get().getCluster(request.getClusterName());
      Set<String> installedServices = cluster.getServices().keySet();
  
      List<String> noUpgradeSupportServices = getNoUpgradeSupportServices(request);
      Map<String, String> replacedServices = getReplacedServices(request);
      List<String> removedServices = getRemovedServices(request);
  
      List<String> failReasons = new ArrayList<>();
  
      String reason = getFailReason(result, request);
      for(String service: noUpgradeSupportServices){
        if (installedServices.contains(service.toUpperCase())){
          result.getFailedOn().add(service);
          String msg = String.format(reason, service, service);
          failReasons.add(msg);
        }
      }
  
      reason = getFailReason(KEY_SERVICE_REPLACED, result, request);
      for (Map.Entry<String, String> entry : replacedServices.entrySet()) {
        String removedService = entry.getKey();
        if(installedServices.contains(removedService.toUpperCase())){
          result.getFailedOn().add(removedService);
          String newService = entry.getValue();
          String msg = String.format(reason, removedService, newService);
          failReasons.add(msg);
        }
      }
  
      reason = getFailReason(KEY_SERVICE_REMOVED, result, request);
      for(String service: removedServices){
        if (installedServices.contains(service.toUpperCase())){
          result.getFailedOn().add(service);
          String msg = String.format(reason, service, service);
          failReasons.add(msg);
        }
      }
  
      if(!failReasons.isEmpty()){
        result.setStatus(UpgradeCheckStatus.FAIL);
        result.setFailReason(StringUtils.join(failReasons, '\n'));
      }
  
      return result;
    }
  
    /**
     * Obtain property value specified in the upgrade XML
     * @return service name
     * */
    private String getPropertyValue(UpgradeCheckRequest request, String propertyKey){
      String value = null;
      Map<String, String> checkProperties = request.getCheckConfigurations();
  
      if(checkProperties != null && checkProperties.containsKey(propertyKey)) {
        value = checkProperties.get(propertyKey);
      }
      return value;
    }
  
    /**
     * @return service names
     * */
    private List<String> getNoUpgradeSupportServices(UpgradeCheckRequest request){
      List<String> result = new ArrayList<>();
      String value = getPropertyValue(request, NO_UPGRADE_SUPPORT_SERVICES_PROPERTY_NAME);
      if (null != value){
        String[] services = value.split(",");
        for(String service: services){
          service = service.trim();
          if (!service.isEmpty()){
            result.add(service);
          }
        }
      }
      return result;
    }
  
    /**
    +   * @return service names
    +   * */
    private List<String> getRemovedServices(UpgradeCheckRequest request){
      List<String> result = new ArrayList<>();
      String value = getPropertyValue(request, REMOVED_SERVICES_PROPERTY_NAME);
      if (null != value){
        String[] services = value.split(",");
        for(String service: services){
          service = service.trim();
          if (!service.isEmpty()){
            result.add(service);
          }
        }
      }
      return result;
    }
  
    /**
     * @return service names and new service names map
     * */
    private Map<String, String> getReplacedServices(UpgradeCheckRequest request) throws AmbariException{
      Map<String, String> result = new LinkedHashMap<>();
      String value = getPropertyValue(request, REPLACED_SERVICES_PROPERTY_NAME);
      String newValue = getPropertyValue(request, NEW_SERVICES_PROPERTY_NAME);
      if(value == null && newValue == null){
        return result; //no need to check removed services as they are not specified in the upgrade xml file.
      } else {
        if (value == null || newValue == null){
          throw new AmbariException(String.format("Both %s and %s list must be specified in the upgrade XML file.", REPLACED_SERVICES_PROPERTY_NAME, NEW_SERVICES_PROPERTY_NAME));
        } else {
          List<String> oldServices = Arrays.asList(value.split(","));
          List<String> newServices = Arrays.asList(newValue.split(","));
          if (oldServices.size() != newServices.size()){
            throw new AmbariException(String.format("%s must have the same number of services as the %s list.", NEW_SERVICES_PROPERTY_NAME, REPLACED_SERVICES_PROPERTY_NAME));
          } else {
            for (int i = 0; i < oldServices.size(); i++){
              String oldService = oldServices.get(i).trim();
              String newService = newServices.get(i).trim();
              if (oldService.isEmpty() || newService.isEmpty()) {
                throw new AmbariException(String.format("Make sure both %s and %s list only contain comma separated list of services.", NEW_SERVICES_PROPERTY_NAME, REPLACED_SERVICES_PROPERTY_NAME));
              } else {
                result.put(oldService, newService);
              }
            }
          }
        }
      }
      return result;
    }
  }
  