private void processConfigurationsIfRequired(UpgradeContext upgradeContext)
      throws AmbariException {

    AmbariManagementController controller = m_controllerProvider.get();

    Cluster cluster = upgradeContext.getCluster();
    Direction direction = upgradeContext.getDirection();
    String userName = controller.getAuthName();
    Set<String> servicesInUpgrade = upgradeContext.getSupportedServices();

    Set<String> clusterConfigTypes = new HashSet<>();
    Set<String> processedClusterConfigTypes = new HashSet<>();
    boolean configsChanged = false;

    // merge or revert configurations for any service that needs it
    for (String serviceName : servicesInUpgrade) {
      RepositoryVersionEntity sourceRepositoryVersion = upgradeContext.getSourceRepositoryVersion(serviceName);
      RepositoryVersionEntity targetRepositoryVersion = upgradeContext.getTargetRepositoryVersion(serviceName);
      StackId sourceStackId = sourceRepositoryVersion.getStackId();
      StackId targetStackId = targetRepositoryVersion.getStackId();

      // only work with configurations when crossing stacks
      if (sourceStackId.equals(targetStackId)) {
        RepositoryVersionEntity associatedRepositoryVersion = upgradeContext.getRepositoryVersion();
        LOG.info(
            "The {} {} {} will not change stack configurations for {} since the source and target are both {}",
            direction.getText(false), direction.getPreposition(),
            associatedRepositoryVersion.getVersion(), serviceName, targetStackId);

        continue;
      }

      ConfigHelper configHelper = m_configHelperProvider.get();

      // downgrade is easy - just remove the new and make the old current
      if (direction == Direction.DOWNGRADE) {
        cluster.applyLatestConfigurations(targetStackId, serviceName);
        configsChanged = true;
        continue;
      }

      // the auto-merge must take read-only properties even if they have changed
      // - if the properties was read-only in the source stack, then we must
      // take the new stack's value
      Map<String, Set<String>> readOnlyProperties = getReadOnlyProperties(sourceStackId, serviceName);

      // upgrade is a bit harder - we have to merge new stack configurations in

      // populate a map of default configurations for the service on the old
      // stack (this is used when determining if a property has been
      // customized and should be overriden with the new stack value)
      Map<String, Map<String, String>> oldServiceDefaultConfigsByType = configHelper.getDefaultProperties(
          sourceStackId, serviceName);

      // populate a map with default configurations from the new stack
      Map<String, Map<String, String>> newServiceDefaultConfigsByType = configHelper.getDefaultProperties(
          targetStackId, serviceName);

      if (null == oldServiceDefaultConfigsByType || null == newServiceDefaultConfigsByType) {
        continue;
      }

      Set<String> foundConfigTypes = new HashSet<>();

      // find the current, existing configurations for the service
      List<Config> existingServiceConfigs = new ArrayList<>();
      List<ServiceConfigEntity> latestServiceConfigs = m_serviceConfigDAO.getLastServiceConfigsForService(
          cluster.getClusterId(), serviceName);

      for (ServiceConfigEntity serviceConfig : latestServiceConfigs) {
        List<ClusterConfigEntity> existingConfigurations = serviceConfig.getClusterConfigEntities();
        for (ClusterConfigEntity currentServiceConfig : existingConfigurations) {
          String configurationType = currentServiceConfig.getType();

          Config currentClusterConfigForService = cluster.getDesiredConfigByType(configurationType);
          if (currentClusterConfigForService == null) {
            throw new AmbariException(String.format("configuration type %s did not have a selected version", configurationType));
          }
          existingServiceConfigs.add(currentClusterConfigForService);
          foundConfigTypes.add(configurationType);
        }
      }

      // !!! these are the types that come back from the config helper, but are not part of the service.
      @SuppressWarnings("unchecked")
      Set<String> missingConfigTypes = new HashSet<>(CollectionUtils.subtract(oldServiceDefaultConfigsByType.keySet(),
          foundConfigTypes));

      for (String missingConfigType : missingConfigTypes) {
        Config config = cluster.getDesiredConfigByType(missingConfigType);
        if (null != config) {
          existingServiceConfigs.add(config);
          clusterConfigTypes.add(missingConfigType);
        }
      }

      // now that we have found, old, new, and existing confgs, overlay the
      // existing on top of the new
      for (Config existingServiceConfig : existingServiceConfigs) {
        String configurationType = existingServiceConfig.getType();

        // get current stack default configurations on install
        Map<String, String> oldServiceDefaultConfigs = oldServiceDefaultConfigsByType.get(
            configurationType);

        // NPE sanity for current stack defaults
        if (null == oldServiceDefaultConfigs) {
          oldServiceDefaultConfigs = Collections.emptyMap();
        }

        // get the existing configurations
        Map<String, String> existingConfigurations = existingServiceConfig.getProperties();

        // get the new configurations
        Map<String, String> newDefaultConfigurations = newServiceDefaultConfigsByType.get(configurationType);

        // if the new stack configurations don't have the type, then simply add
        // all of the existing in
        if (null == newDefaultConfigurations) {
          newServiceDefaultConfigsByType.put(configurationType, existingConfigurations);
          continue;
        } else {
          // Remove any configs in the new stack whose value is NULL, unless
          // they currently exist and the value is not NULL.
          Iterator<Map.Entry<String, String>> iter = newDefaultConfigurations.entrySet().iterator();
          while (iter.hasNext()) {
            Map.Entry<String, String> entry = iter.next();
            if (entry.getValue() == null) {
              iter.remove();
            }
          }
        }

        // process every existing configuration property for this configuration type
        for (Map.Entry<String, String> existingConfigurationEntry : existingConfigurations.entrySet()) {
          String existingConfigurationKey = existingConfigurationEntry.getKey();
          String existingConfigurationValue = existingConfigurationEntry.getValue();

          // if there is already an entry, we now have to try to determine if
          // the value was customized after stack installation
          if (newDefaultConfigurations.containsKey(existingConfigurationKey)) {
            String newDefaultConfigurationValue = newDefaultConfigurations.get(
                existingConfigurationKey);

            if (!StringUtils.equals(existingConfigurationValue, newDefaultConfigurationValue)) {
              // the new default is different from the existing cluster value;
              // only override the default value if the existing value differs
              // from the original stack
              String oldDefaultValue = oldServiceDefaultConfigs.get(existingConfigurationKey);

              // see if this property is a read-only property which means that
              // we shouldn't care if it was changed - we should take the new
              // stack's value
              Set<String> readOnlyPropertiesForType = readOnlyProperties.get(configurationType);
              boolean readOnly = (null != readOnlyPropertiesForType
                  && readOnlyPropertiesForType.contains(existingConfigurationKey));

              if (!readOnly && !StringUtils.equals(existingConfigurationValue, oldDefaultValue)) {
                // at this point, we've determined that there is a difference
                // between default values between stacks, but the value was also
                // customized, so keep the customized value
                newDefaultConfigurations.put(existingConfigurationKey, existingConfigurationValue);
              }
            }
          } else {
            // there is no entry in the map, so add the existing key/value pair
            newDefaultConfigurations.put(existingConfigurationKey, existingConfigurationValue);
          }
        }

        /*
        for every new configuration which does not exist in the existing
        configurations, see if it was present in the current stack

        stack 2.x has foo-site/property (on-ambari-upgrade is false)
        stack 2.y has foo-site/property
        the current cluster (on 2.x) does not have it

        In this case, we should NOT add it back as clearly stack advisor has removed it
        */
        Iterator<Map.Entry<String, String>> newDefaultConfigurationsIterator = newDefaultConfigurations.entrySet().iterator();
        while (newDefaultConfigurationsIterator.hasNext()) {
          Map.Entry<String, String> newConfigurationEntry = newDefaultConfigurationsIterator.next();
          String newConfigurationPropertyName = newConfigurationEntry.getKey();
          if (oldServiceDefaultConfigs.containsKey(newConfigurationPropertyName)
              && !existingConfigurations.containsKey(newConfigurationPropertyName)) {
            LOG.info(
                "The property {}/{} exists in both {} and {} but is not part of the current set of configurations and will therefore not be included in the configuration merge",
                configurationType, newConfigurationPropertyName, sourceStackId, targetStackId);

            // remove the property so it doesn't get merged in
            newDefaultConfigurationsIterator.remove();
          }
        }
      }

      for (String clusterConfigType : clusterConfigTypes) {
        if (processedClusterConfigTypes.contains(clusterConfigType)) {
          newServiceDefaultConfigsByType.remove(clusterConfigType);
        } else {
          processedClusterConfigTypes.add(clusterConfigType);
        }

      }

      Set<String> configTypes = newServiceDefaultConfigsByType.keySet();
      LOG.warn("The upgrade will create the following configurations for stack {}: {}",
          targetStackId, StringUtils.join(configTypes, ','));

      String serviceVersionNote = String.format("%s %s %s", direction.getText(true),
          direction.getPreposition(), upgradeContext.getRepositoryVersion().getVersion());

      configHelper.createConfigTypes(cluster, targetStackId, controller,
          newServiceDefaultConfigsByType, userName, serviceVersionNote);
      configsChanged = true;
    }
    if (configsChanged) {
      m_configHelperProvider.get().updateAgentConfigs(Collections.singleton(cluster.getClusterName()));
    }
  }
Handles the creation or resetting of configurations based on whether an
upgrade or downgrade is occurring. This method will not do anything when
the service is not crossing major stack versions, since, by definition, no
new configurations are automatically created when upgrading with the same
stack (ie HDP 2.2.0.0 -> HDP 2.2.1.0).
<p/>
When upgrading or downgrade between stacks (HDP 2.2.0.0 -> HDP 2.3.0.0)
then this will perform the following:
<ul>
<li>Upgrade: Create new configurations that are a merge between the source
stack and the target stack. If a value has changed between stacks, then the
target stack value should be taken unless the cluster's value differs from
the old stack. This can occur if a property has been customized after
installation. Read-only properties, however, are always taken from the new
stack.</li>
<li>Downgrade: Reset the latest configurations from the service's original
stack. The new configurations that were created on upgrade must be left
intact until all components have been reverted, otherwise heartbeats will
fail due to missing configurations.</li>
</ul>

@param upgradeContext
         the upgrade context (not {@code null}).
@throws AmbariException
ambari-server/src/main/java/org/apache/ambari/server/stack/upgrade/orchestrate/UpgradeHelper.java