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.serveraction.upgrades;
  
  import static org.apache.ambari.server.controller.KerberosHelper.DEFAULT_REALM;
  import static org.apache.ambari.server.controller.KerberosHelper.KERBEROS_ENV;
  import static org.apache.ambari.server.controller.KerberosHelper.PRECONFIGURE_SERVICES;
  
  import java.io.IOException;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.ConcurrentMap;
  
  import org.apache.ambari.server.AmbariException;
  import org.apache.ambari.server.actionmanager.HostRoleStatus;
  import org.apache.ambari.server.agent.CommandReport;
  import org.apache.ambari.server.controller.AmbariManagementController;
  import org.apache.ambari.server.controller.KerberosHelper;
  import org.apache.ambari.server.controller.RootComponent;
  import org.apache.ambari.server.controller.RootService;
  import org.apache.ambari.server.orm.dao.HostDAO;
  import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO;
  import org.apache.ambari.server.orm.entities.HostEntity;
  import org.apache.ambari.server.orm.entities.KerberosKeytabPrincipalEntity;
  import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
  import org.apache.ambari.server.serveraction.kerberos.PreconfigureServiceType;
  import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerberosKeytab;
  import org.apache.ambari.server.stack.upgrade.Direction;
  import org.apache.ambari.server.stack.upgrade.orchestrate.UpgradeContext;
  import org.apache.ambari.server.state.Cluster;
  import org.apache.ambari.server.state.ConfigHelper;
  import org.apache.ambari.server.state.Host;
  import org.apache.ambari.server.state.SecurityType;
  import org.apache.ambari.server.state.Service;
  import org.apache.ambari.server.state.ServiceComponentHost;
  import org.apache.ambari.server.state.StackId;
  import org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer;
  import org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor;
  import org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor;
  import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
  import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor;
  import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
  import org.apache.ambari.server.state.kerberos.VariableReplacementHelper;
  import org.apache.ambari.server.utils.StageUtils;
  import org.apache.commons.collections4.CollectionUtils;
  import org.apache.commons.collections4.MapUtils;
  import org.apache.commons.lang.StringUtils;
  
  import com.google.inject.Inject;
  
  /**
   * PreconfigureKerberos updates existing service configurations with properties from service-level
   * Kerberos descriptors, flagged for pre-configuring, during stack upgrades in order to prevent service
   * restarts when the flagged services are installed.
   */
  public class PreconfigureKerberosAction extends AbstractUpgradeServerAction {
    static final String UPGRADE_DIRECTION_KEY = "upgrade_direction";
  
    @Inject
    private AmbariManagementController ambariManagementController;
  
    @Inject
    private KerberosHelper kerberosHelper;
  
    @Inject
    private ConfigHelper configHelper;
  
    @Inject
    private VariableReplacementHelper variableReplacementHelper;
  
    @Inject
    private HostDAO hostDAO;
  
    @Inject
    private KerberosKeytabPrincipalDAO kerberosKeytabPrincipalDAO;
  
    @Override
    public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext) throws AmbariException, InterruptedException {
      Map<String, String> commandParameters = getCommandParameters();
      if (null == commandParameters || commandParameters.isEmpty()) {
        return createCommandReport(0, HostRoleStatus.FAILED, "{}", "",
            "Unable to change configuration values without command parameters");
      }
  
      if (!isDowngrade()) {
        String clusterName = commandParameters.get("clusterName");
        Cluster cluster = getClusters().getCluster(clusterName);
  
        if (cluster.getSecurityType() == SecurityType.KERBEROS) {
          StackId stackId;
  
          try {
            stackId = getTargetStackId(cluster);
          } catch (AmbariException e) {
            return createCommandReport(0, HostRoleStatus.FAILED, "{}", "", e.getLocalizedMessage());
          }
  
          if (stackId == null) {
            return createCommandReport(0, HostRoleStatus.FAILED, "{}", "",
                "The target stack Id was not specified.");
          }
  
          KerberosDescriptor kerberosDescriptor = kerberosHelper.getKerberosDescriptor(KerberosHelper.KerberosDescriptorType.COMPOSITE, cluster, stackId, true, null);
  
          // Calculate the current host-specific configurations. These will be used to replace
          // variables within the Kerberos descriptor data
          Map<String, Map<String, String>> configurations = kerberosHelper.calculateConfigurations(cluster, null, kerberosDescriptor, true, false, null);
  
          PreconfigureServiceType preconfigureServiceType = getPreconfigureServiceType(configurations);
  
          if (preconfigureServiceType != PreconfigureServiceType.NONE) {
            Map<String, Map<String, String>> kerberosConfigurations = new HashMap<>();
            Map<String, Set<String>> propertiesToRemove = new HashMap<>();
            Map<String, Set<String>> propertiesToIgnore = new HashMap<>();
  
            if (preconfigureServiceType == PreconfigureServiceType.ALL) {
              // Force all services to be flagged for pre-configuration...
              Map<String, KerberosServiceDescriptor> serviceDescriptors = kerberosDescriptor.getServices();
              if (serviceDescriptors != null) {
                for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors.values()) {
                  serviceDescriptor.setPreconfigure(true);
                }
              }
            }
  
            processServiceComponentHosts(cluster, kerberosDescriptor, configurations, kerberosConfigurations, propertiesToIgnore, getDefaultRealm(configurations));
  
            // Calculate the set of configurations to update and replace any variables
            // using the previously calculated Map of configurations for the host.
            kerberosConfigurations = kerberosHelper.processPreconfiguredServiceConfigurations(kerberosConfigurations, configurations, cluster, kerberosDescriptor);
  
            Map<String, Set<String>> installedServices = calculateInstalledServices(cluster);
  
            kerberosHelper.applyStackAdvisorUpdates(cluster, installedServices.keySet(), configurations, kerberosConfigurations,
                propertiesToIgnore, propertiesToRemove, true);
  
            kerberosHelper.setAuthToLocalRules(cluster, kerberosDescriptor, getDefaultRealm(configurations), installedServices,
                configurations, kerberosConfigurations, true);
  
            processConfigurationChanges(cluster, stackId, kerberosDescriptor, kerberosConfigurations, propertiesToRemove, configurations);
          } else {
            actionLog.writeStdOut("Skipping: This facility is only available when kerberos-env/preconfigure_services is not \"NONE\"");
          }
        } else {
          actionLog.writeStdOut("Skipping: This facility is only available when Kerberos is enabled");
        }
      } else {
        actionLog.writeStdOut("Skipping: This facility is only available during an upgrade");
      }
  
      return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", actionLog.getStdOut(), actionLog.getStdErr());
    }
  
    /**
     * Given a Cluster object creates a map of service names to sets of the installed components for that
     * service.
     *
     * @param cluster the cluster
     * @return a map of (installed) service names to the relevant set of (installed) component names
     */
    private Map<String, Set<String>> calculateInstalledServices(Cluster cluster) {
      Map<String, Set<String>> installedServices = new HashMap<>();
      Map<String, Service> services = cluster.getServices();
  
      for (Service service : services.values()) {
        installedServices.put(service.getName(), service.getServiceComponents().keySet());
      }
  
      return installedServices;
    }
  
    /**
     * Safely retrieves the specified property from the specified configuration type from a map of
     * configurations.
     *
     * @param configurations the existing configurations for the cluster
     * @return the requested value or null if the configuration does not exist
     */
    private String getValueFromConfiguration(Map<String, Map<String, String>> configurations, String configType, String propertyName) {
      String value = null;
  
      if (configurations != null) {
        Map<String, String> kerberosEnv = configurations.get(configType);
  
        if (kerberosEnv != null) {
          value = kerberosEnv.get(propertyName);
        }
      }
  
      return value;
    }
  
    /**
     * Safely retrieves the <code>realm</code> property of the <code>kerberos-env</code> configuration.
     *
     * @param configurations the existing configurations for the cluster
     * @return the requested value or null if the configuration does not exist
     * @see #getValueFromConfiguration(Map, String, String)
     */
    private String getDefaultRealm(Map<String, Map<String, String>> configurations) {
      return getValueFromConfiguration(configurations, KERBEROS_ENV, DEFAULT_REALM);
    }
  
    /**
     * Safely retrieves the <code>preconfigure_services</code> property of the <code>kerberos-env</code> configuration.
     *
     * @param configurations the existing configurations for the cluster
     * @return the requested value or null if the configuration does not exist
     * @see #getValueFromConfiguration(Map, String, String)
     */
    private PreconfigureServiceType getPreconfigureServiceType(Map<String, Map<String, String>> configurations) {
      String preconfigureServices = getValueFromConfiguration(configurations, KERBEROS_ENV, PRECONFIGURE_SERVICES);
  
      PreconfigureServiceType preconfigureServiceType = null;
      if (!StringUtils.isEmpty(preconfigureServices)) {
        try {
          preconfigureServiceType = PreconfigureServiceType.valueOf(preconfigureServices.toUpperCase());
        } catch (Throwable t) {
          preconfigureServiceType = PreconfigureServiceType.DEFAULT;
        }
      }
  
      return (preconfigureServiceType == null) ? PreconfigureServiceType.DEFAULT : preconfigureServiceType;
    }
  
    /**
     * Determines if upgrade direction is {@link Direction#UPGRADE} or {@link Direction#DOWNGRADE}.
     *
     * @return {@code true} if {@link Direction#DOWNGRADE}; {@code false} if {@link Direction#UPGRADE}
     */
    private boolean isDowngrade() {
      return Direction.DOWNGRADE.name().equalsIgnoreCase(getCommandParameterValue(UPGRADE_DIRECTION_KEY));
    }
  
    /**
     * Retrieves the target stack ID for the stack upgrade or downgrade operation.
     *
     * @param cluster the cluster
     * @return the target {@link StackId}
     * @throws AmbariException if multiple stack id's are detected
     */
    private StackId getTargetStackId(Cluster cluster) throws AmbariException {
      UpgradeContext upgradeContext = getUpgradeContext(cluster);
  
      // !!! FIXME in a per-service view, what does this become?
      Set<StackId> stackIds = new HashSet<>();
  
      for (Service service : cluster.getServices().values()) {
        RepositoryVersionEntity targetRepoVersion = upgradeContext.getTargetRepositoryVersion(service.getName());
        StackId targetStackId = targetRepoVersion.getStackId();
        stackIds.add(targetStackId);
      }
  
      if (1 != stackIds.size()) {
        throw new AmbariException("Services are deployed from multiple stacks and cannot determine a unique one.");
      }
  
      return stackIds.iterator().next();
    }
  
    /**
     * Find and iterate through the {@link ServiceComponentHost} objects for the current {@link Cluster}
     * to calculate property updates and auth-to-local rules.
     *
     * @param cluster                the cluster
     * @param kerberosDescriptor     the Kerberos descriptor
     * @param currentConfigurations  the current configurations for the cluster
     * @param kerberosConfigurations the (Kerberos-specific) configuration updates
     * @param propertiesToBeIgnored  a map to store properties that should be ignored by operations that update property values
     * @throws AmbariException if an issue occurs
     */
    private void processServiceComponentHosts(Cluster cluster, KerberosDescriptor kerberosDescriptor,
                                              Map<String, Map<String, String>> currentConfigurations,
                                              Map<String, Map<String, String>> kerberosConfigurations,
                                              Map<String, Set<String>> propertiesToBeIgnored, String realm)
        throws AmbariException {
  
      Collection<Host> hosts = cluster.getHosts();
      if (!hosts.isEmpty()) {
        // Create the context to use for filtering Kerberos Identities based on the state of the cluster
        Map<String, Object> filterContext = new HashMap<>();
        filterContext.put("configurations", currentConfigurations);
        filterContext.put("services", cluster.getServices().keySet());
  
        try {
          Map<String, Set<String>> propertiesToIgnore = null;
          HashMap<String, ResolvedKerberosKeytab> resolvedKeytabs = new HashMap<>();
          for (Host host : hosts) {
            // Iterate over the components installed on the current host to get the service and
            // component-level Kerberos descriptors in order to determine which principals,
            // keytab files, and configurations need to be created or updated.
            for (ServiceComponentHost sch : cluster.getServiceComponentHosts(host.getHostName())) {
              String hostName = sch.getHostName();
  
              String serviceName = sch.getServiceName();
              String componentName = sch.getServiceComponentName();
  
              KerberosServiceDescriptor serviceDescriptor = kerberosDescriptor.getService(serviceName);
  
              if (!StringUtils.isEmpty(hostName)) {
                // Update the configurations with the relevant hostname
                Map<String, String> generalProperties = currentConfigurations.get("");
                if (generalProperties == null) {
                  generalProperties = new HashMap<>();
                  currentConfigurations.put("", generalProperties);
                }
  
                // Add the current hostname under "host" and "hostname"
                generalProperties.put("host", hostName);
                generalProperties.put("hostname", hostName);
              }
  
              if (serviceDescriptor != null) {
                List<KerberosIdentityDescriptor> serviceIdentities = serviceDescriptor.getIdentities(true, filterContext);
  
                // Add service-level principals (and keytabs)
                kerberosHelper.addIdentities(null, serviceIdentities,
                    null, hostName, host.getHostId(), serviceName, componentName, kerberosConfigurations, currentConfigurations,
                    resolvedKeytabs, realm);
                propertiesToIgnore = gatherPropertiesToIgnore(serviceIdentities, propertiesToIgnore);
  
                KerberosComponentDescriptor componentDescriptor = serviceDescriptor.getComponent(componentName);
  
                if (componentDescriptor != null) {
                  List<KerberosIdentityDescriptor> componentIdentities = componentDescriptor.getIdentities(true, filterContext);
  
                  // Calculate the set of configurations to update and replace any variables
                  // using the previously calculated Map of configurations for the host.
                  kerberosHelper.mergeConfigurations(kerberosConfigurations,
                      componentDescriptor.getConfigurations(true), currentConfigurations, null);
  
                  // Add component-level principals (and keytabs)
                  kerberosHelper.addIdentities(null, componentIdentities,
                      null, hostName, host.getHostId(), serviceName, componentName, kerberosConfigurations, currentConfigurations,
                      resolvedKeytabs,realm);
                  propertiesToIgnore = gatherPropertiesToIgnore(componentIdentities, propertiesToIgnore);
                }
              }
            }
          }
  
          // Add ambari-server identities only if 'kerberos-env.create_ambari_principal = true'
          if (kerberosHelper.createAmbariIdentities(currentConfigurations.get(KERBEROS_ENV))) {
            List<KerberosIdentityDescriptor> ambariIdentities = kerberosHelper.getAmbariServerIdentities(kerberosDescriptor);
  
            for (KerberosIdentityDescriptor identity : ambariIdentities) {
              // If the identity represents the ambari-server user, use the component name "AMBARI_SERVER_SELF"
              // so it can be distinguished between other identities related to the AMBARI-SERVER
              // component.
              String componentName = KerberosHelper.AMBARI_SERVER_KERBEROS_IDENTITY_NAME.equals(identity.getName())
                  ? "AMBARI_SERVER_SELF"
                  : RootComponent.AMBARI_SERVER.name();
  
              List<KerberosIdentityDescriptor> componentIdentities = Collections.singletonList(identity);
              kerberosHelper.addIdentities(null, componentIdentities,
                  null, KerberosHelper.AMBARI_SERVER_HOST_NAME, ambariServerHostID(), RootService.AMBARI.name(), componentName, kerberosConfigurations, currentConfigurations,
                  resolvedKeytabs, realm);
              propertiesToIgnore = gatherPropertiesToIgnore(componentIdentities, propertiesToIgnore);
            }
          }
  
          if ((propertiesToBeIgnored != null) && (propertiesToIgnore != null)) {
            propertiesToBeIgnored.putAll(propertiesToIgnore);
          }
  
          // create database records for keytab that must be presented on cluster
          List<KerberosKeytabPrincipalEntity> keytabList = kerberosKeytabPrincipalDAO.findAll();
          resolvedKeytabs.values().forEach(keytab -> kerberosHelper.createResolvedKeytab(keytab, keytabList));
        } catch (IOException e) {
          throw new AmbariException(e.getMessage(), e);
        }
      }
    }
  
    private Map<String, Set<String>> gatherPropertiesToIgnore(List<KerberosIdentityDescriptor> identities,
                                                              Map<String, Set<String>> propertiesToIgnore) {
      Map<String, Map<String, String>> identityConfigurations = kerberosHelper.getIdentityConfigurations(identities);
      if (!MapUtils.isEmpty(identityConfigurations)) {
        if (propertiesToIgnore == null) {
          propertiesToIgnore = new HashMap<>();
        }
  
        for (Map.Entry<String, Map<String, String>> entry : identityConfigurations.entrySet()) {
          String configType = entry.getKey();
          Map<String, String> properties = entry.getValue();
  
          if (MapUtils.isEmpty(properties)) {
            Set<String> propertyNames = propertiesToIgnore.get(configType);
            if (propertyNames == null) {
              propertyNames = new HashSet<>();
              propertiesToIgnore.put(configType, propertyNames);
            }
            propertyNames.addAll(properties.keySet());
          }
        }
      }
  
      return propertiesToIgnore;
    }
  
    /**
     * Processes configuration changes to determine if any work needs to be done.
     * <p/>
     * If work is to be done, a data file containing the details is created so it they changes may be
     * processed in the appropriate stage.
     *
     * @param cluster                the cluster
     * @param targetStackId          the target stack id
     * @param kerberosConfigurations the Kerberos-specific configuration map
     * @param propertiesToBeRemoved  a map of properties to be removed from the current configuration,
     *                               grouped by configuration type.
     * @param variableReplaments     replacement values to use when attempting to perform variable replacements on the property names
     * @throws AmbariException if an issue is encountered
     */
    private void processConfigurationChanges(Cluster cluster, StackId targetStackId,
                                             KerberosDescriptor kerberosDescriptor,
                                             Map<String, Map<String, String>> kerberosConfigurations,
                                             Map<String, Set<String>> propertiesToBeRemoved,
                                             Map<String, Map<String, String>> variableReplaments)
        throws AmbariException {
      actionLog.writeStdOut("Determining configuration changes");
  
      if (!kerberosConfigurations.isEmpty()) {
        Map<String, Service> installedServices = cluster.getServices();
  
        // Build a map of configuration types to properties that indicate which properties should be altered
        // This map should contain only properties defined in service-level Kerberos descriptors that
        // have been flagged to be preconfigured and that have not yet been installed.
        Map<String, Set<String>> propertyFilter = new HashMap<>();
        Map<String, KerberosServiceDescriptor> serviceDescriptors = kerberosDescriptor.getServices();
        if (serviceDescriptors != null) {
          for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors.values()) {
            if (!installedServices.containsKey(serviceDescriptor.getName()) && serviceDescriptor.shouldPreconfigure()) {
              buildFilter(Collections.singleton(serviceDescriptor), propertyFilter, variableReplaments);
            }
          }
        }
  
        // Add the auth-to-local rule configuration specifications to the filter
        Map<String, Set<String>> authToLocalProperties = kerberosHelper.translateConfigurationSpecifications(kerberosDescriptor.getAllAuthToLocalProperties());
        if (!MapUtils.isEmpty(authToLocalProperties)) {
          for (Map.Entry<String, Set<String>> entry : authToLocalProperties.entrySet()) {
            Set<String> properties = entry.getValue();
  
            if (!CollectionUtils.isEmpty(properties)) {
              String configurationType = entry.getKey();
  
              Set<String> propertyNames = propertyFilter.get(configurationType);
              if (propertyNames == null) {
                propertyNames = new HashSet<>();
                propertyFilter.put(configurationType, propertyNames);
              }
  
              propertyNames.addAll(properties);
            }
          }
        }
  
        Set<String> visitedTypes = new HashSet<>();
  
        for (Map.Entry<String, Map<String, String>> entry : kerberosConfigurations.entrySet()) {
          String configType = entry.getKey();
  
          String service = cluster.getServiceByConfigType(configType);
          Set<String> allowedProperties = propertyFilter.get(configType);
  
          // Update properties for services that are installed and not filtered out
          if (installedServices.containsKey(service) && !CollectionUtils.isEmpty(allowedProperties)) {
            Map<String, String> propertiesToUpdate = entry.getValue();
            Set<String> propertiesToRemove = (propertiesToBeRemoved == null) ? null : propertiesToBeRemoved.get(configType);
  
            // Filter the properties to update
            if (propertiesToUpdate != null) {
              Iterator<Map.Entry<String, String>> mapIterator = propertiesToUpdate.entrySet().iterator();
              while (mapIterator.hasNext()) {
                Map.Entry<String, String> mapEntry = mapIterator.next();
  
                if (!allowedProperties.contains(mapEntry.getKey())) {
                  mapIterator.remove();
                }
              }
            }
  
            // Filter the properties to remove
            if (propertiesToRemove != null) {
              Iterator<String> setIterator = propertiesToRemove.iterator();
              while (setIterator.hasNext()) {
                String setEntry = setIterator.next();
                if (!allowedProperties.contains(setEntry)) {
                  setIterator.remove();
                }
              }
            }
  
            visitedTypes.add(configType);
  
            if (!MapUtils.isEmpty(propertiesToUpdate) || !CollectionUtils.isEmpty(propertiesToRemove)) {
              if (!MapUtils.isEmpty(propertiesToUpdate)) {
                for (Map.Entry<String, String> property : propertiesToUpdate.entrySet()) {
                  actionLog.writeStdOut(String.format("Setting: %s/%s = %s", configType, property.getKey(), property.getValue()));
                }
              }
  
              if (!CollectionUtils.isEmpty(propertiesToRemove)) {
                for (String property : propertiesToRemove) {
                  actionLog.writeStdOut(String.format("Removing: %s/%s", configType, property));
                }
              }
  
              configHelper.updateConfigType(cluster, targetStackId,
                  ambariManagementController, configType, propertiesToUpdate, propertiesToRemove,
                  ambariManagementController.getAuthName(), "Preconfiguring for Kerberos during upgrade");
            }
          }
        }
  
        if (!MapUtils.isEmpty(propertiesToBeRemoved)) {
          for (Map.Entry<String, Set<String>> entry : propertiesToBeRemoved.entrySet()) {
            String configType = entry.getKey();
  
            if (!visitedTypes.contains(configType)) {
              Set<String> propertiesToRemove = entry.getValue();
  
              if (!CollectionUtils.isEmpty(propertiesToRemove)) {
                for (String property : propertiesToRemove) {
                  actionLog.writeStdOut(String.format("Removing: %s/%s", configType, property));
                }
  
                configHelper.updateConfigType(cluster, targetStackId,
                    ambariManagementController, configType, null, entry.getValue(),
                    ambariManagementController.getAuthName(), "Preconfiguring for Kerberos during upgrade");
              }
            }
          }
        }
      }
    }
  
    /**
     * Adds entries to the property filter (<code>propertyFilter</code>) found in the {@link KerberosConfigurationDescriptor}s
     * within the specified node of the Kerberos descriptor.
     *
     * @param containers     the Kerberos descriptor containers to process
     * @param propertyFilter the property filter map to update
     * @param replacements   replacement values to use when attempting to perform variable replacements on the property names
     * @throws AmbariException if an issue occurs while replacing variables in the property names
     */
    private void buildFilter(Collection<? extends AbstractKerberosDescriptorContainer> containers,
                             Map<String, Set<String>> propertyFilter,
                             Map<String, Map<String, String>> replacements)
        throws AmbariException {
      if (containers != null) {
        for (AbstractKerberosDescriptorContainer container : containers) {
          Map<String, KerberosConfigurationDescriptor> configurationDescriptors = container.getConfigurations(false);
  
          if (!MapUtils.isEmpty(configurationDescriptors)) {
            for (KerberosConfigurationDescriptor configurationDescriptor : configurationDescriptors.values()) {
              Map<String, String> properties = configurationDescriptor.getProperties();
  
              if (!MapUtils.isEmpty(properties)) {
                String configType = configurationDescriptor.getType();
  
                Set<String> propertyNames = propertyFilter.get(configType);
                if (propertyNames == null) {
                  propertyNames = new HashSet<>();
                  propertyFilter.put(configType, propertyNames);
                }
  
                // Replace variables in the property name. For example ${knox-env/knox_user}.
                for (String propertyName : properties.keySet()) {
                  propertyNames.add(variableReplacementHelper.replaceVariables(propertyName, replacements));
                }
              }
            }
          }
  
          Collection<? extends AbstractKerberosDescriptorContainer> childContainers = container.getChildContainers();
          if (childContainers != null) {
            buildFilter(childContainers, propertyFilter, replacements);
          }
        }
      }
    }
  
    protected Long ambariServerHostID(){
      String ambariServerHostName = StageUtils.getHostName();
      HostEntity ambariServerHostEntity = hostDAO.findByName(ambariServerHostName);
      return (ambariServerHostEntity == null)
          ? null
          : ambariServerHostEntity.getHostId();
    }
  
  }
  
  