text: #!/usr/bin/env python
  """
  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.
  
  """
  
  __all__ = ["select", "create", "get_hadoop_conf_dir", "get_hadoop_dir", "get_package_dirs"]
  
  # Python Imports
  import copy
  import os
  import subprocess
  
  # Local Imports
  import version
  import stack_select
  from resource_management.core import shell
  from resource_management.libraries.functions.format import format
  from resource_management.libraries.script.script import Script
  from resource_management.core.logger import Logger
  from resource_management.core.resources.system import Directory
  from resource_management.core.resources.system import Execute
  from resource_management.core.resources.system import Link
  from resource_management.libraries.functions.default import default
  from resource_management.libraries.functions import stack_tools
  from resource_management.core.exceptions import Fail
  from resource_management.core.shell import as_sudo
  from resource_management.libraries.functions.stack_features import check_stack_feature
  from resource_management.libraries.functions import StackFeature
  
  STACK_ROOT_PATTERN = "{{ stack_root }}"
  
  _PACKAGE_DIRS = {
    "atlas": [
      {
        "conf_dir": "/etc/atlas/conf",
        "current_dir": "{0}/current/atlas-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "accumulo": [
      {
        "conf_dir": "/etc/accumulo/conf",
        "current_dir": "{0}/current/accumulo-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "falcon": [
      {
        "conf_dir": "/etc/falcon/conf",
        "current_dir": "{0}/current/falcon-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "hadoop": [
      {
        "conf_dir": "/etc/hadoop/conf",
        "current_dir": "{0}/current/hadoop-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "hbase": [
      {
        "conf_dir": "/etc/hbase/conf",
        "current_dir": "{0}/current/hbase-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "hive": [
      {
        "conf_dir": "/etc/hive/conf",
        "current_dir": "{0}/current/hive-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "hive2": [
      {
        "conf_dir": "/etc/hive2/conf",
        "current_dir": "{0}/current/hive-server2-hive2/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "kafka": [
      {
        "conf_dir": "/etc/kafka/conf",
        "current_dir": "{0}/current/kafka-broker/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "knox": [
      {
        "conf_dir": "/etc/knox/conf",
        "current_dir": "{0}/current/knox-server/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "mahout": [
      {
        "conf_dir": "/etc/mahout/conf",
        "current_dir": "{0}/current/mahout-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "nifi": [
      {
        "conf_dir": "/etc/nifi/conf",
        "current_dir": "{0}/current/nifi/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "oozie": [
      {
        "conf_dir": "/etc/oozie/conf",
        "current_dir": "{0}/current/oozie-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "phoenix": [
      {
        "conf_dir": "/etc/phoenix/conf",
        "current_dir": "{0}/current/phoenix-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "ranger-admin": [
      {
        "conf_dir": "/etc/ranger/admin/conf",
        "current_dir": "{0}/current/ranger-admin/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "ranger-tagsync": [
      {
        "conf_dir": "/etc/ranger/tagsync/conf",
        "current_dir": "{0}/current/ranger-tagsync/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "ranger-kms": [
      {
        "conf_dir": "/etc/ranger/kms/conf",
        "current_dir": "{0}/current/ranger-kms/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "ranger-usersync": [
      {
        "conf_dir": "/etc/ranger/usersync/conf",
        "current_dir": "{0}/current/ranger-usersync/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "slider": [
      {
        "conf_dir": "/etc/slider/conf",
        "current_dir": "{0}/current/slider-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "spark": [
      {
        "conf_dir": "/etc/spark/conf",
        "current_dir": "{0}/current/spark-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "zeppelin": [
      {
        "conf_dir": "/etc/zeppelin/conf",
        "current_dir": "{0}/current/zeppelin-server/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "spark2": [
      {
        "conf_dir": "/etc/spark2/conf",
        "current_dir": "{0}/current/spark2-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "sqoop": [
      {
        "conf_dir": "/etc/sqoop/conf",
        "current_dir": "{0}/current/sqoop-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "storm": [
      {
        "conf_dir": "/etc/storm/conf",
        "current_dir": "{0}/current/storm-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "tez": [
      {
        "conf_dir": "/etc/tez/conf",
        "current_dir": "{0}/current/tez-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "zookeeper": [
      {
        "conf_dir": "/etc/zookeeper/conf",
        "current_dir": "{0}/current/zookeeper-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "pig": [
      {
        "conf_dir": "/etc/pig/conf",
        "current_dir": "{0}/current/pig-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "flume": [
      {
        "conf_dir": "/etc/flume/conf",
        "current_dir": "{0}/current/flume-server/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "storm-slider-client": [
      {
        "conf_dir": "/etc/storm-slider-client/conf",
        "current_dir": "{0}/current/storm-slider-client/conf".format(STACK_ROOT_PATTERN)
      }
    ],
    "hive-hcatalog": [
      {
        "conf_dir": "/etc/hive-webhcat/conf",
        "prefix": "/etc/hive-webhcat",
        "current_dir": "{0}/current/hive-webhcat/etc/webhcat".format(STACK_ROOT_PATTERN)
      },
      {
        "conf_dir": "/etc/hive-hcatalog/conf",
        "prefix": "/etc/hive-hcatalog",
        "current_dir": "{0}/current/hive-webhcat/etc/hcatalog".format(STACK_ROOT_PATTERN)
      }
    ]
  }
  
  DIRECTORY_TYPE_BACKUP = "backup"
  DIRECTORY_TYPE_CURRENT = "current"
  
  def _get_cmd(command, package, version):
    conf_selector_path = stack_tools.get_stack_tool_path(stack_tools.CONF_SELECTOR_NAME)
    return ('ambari-python-wrap', conf_selector_path, command, '--package', package, '--stack-version', version, '--conf-version', '0')
  
  def _valid(stack_name, package, ver):
    return (ver and check_stack_feature(StackFeature.CONFIG_VERSIONING, ver))
  
  def get_package_dirs():
    """
    Get package dir mappings
    :return:
    """
    stack_root = Script.get_stack_root()
    package_dirs = copy.deepcopy(_PACKAGE_DIRS)
    for package_name, directories in package_dirs.iteritems():
      for dir in directories:
        current_dir = dir['current_dir']
        current_dir = current_dir.replace(STACK_ROOT_PATTERN, stack_root)
        dir['current_dir'] = current_dir
    return package_dirs
  
  def create(stack_name, package, version, dry_run = False):
    """
    Creates a config version for the specified package
    :param stack_name: the name of the stack
    :param package: the name of the package, as-used by <conf-selector-tool>
    :param version: the version number to create
    :param dry_run: False to create the versioned config directory, True to only return what would be created
    :return List of directories created
    """
    Logger.info("Checking if need to create versioned conf dir /etc/{0}/{1}/0".format(package, version))
    if not _valid(stack_name, package, version):
      Logger.info("Will not create it since parameters are not valid.")
      return []
  
    command = "dry-run-create" if dry_run else "create-conf-dir"
  
    code, stdout, stderr = shell.call(_get_cmd(command, package, version), logoutput=False, quiet=False, sudo=True, stderr = subprocess.PIPE)
  
    # <conf-selector-tool> can set more than one directory
    # per package, so return that list, especially for dry_run
    # > <conf-selector-tool> dry-run-create --package hive-hcatalog --stack-version 2.4.0.0-169 0
    # /etc/hive-webhcat/2.4.0.0-169/0
    # /etc/hive-hcatalog/2.4.0.0-169/0
    created_directories = []
    if 0 == code and stdout is not None: # just be sure we have a stdout
      for line in stdout.splitlines():
        created_directories.append(line.rstrip('\n'))
  
    # if directories were created, then do some post-processing
    if not code and stdout and not dry_run:
      # take care of permissions if directories were created
      for directory in created_directories:
        Directory(directory, mode=0755, cd_access='a', create_parents=True)
  
      # seed the new directories with configurations from the old (current) directories
      _seed_new_configuration_directories(package, created_directories)
  
    return created_directories
  
  
  def select(stack_name, package, version, try_create=True, ignore_errors=False):
    """
    Selects a config version for the specified package. If this detects that
    the stack supports configuration versioning but /etc/<component>/conf is a
    directory, then it will attempt to bootstrap the conf.backup directory and change
    /etc/<component>/conf into a symlink.
  
    :param stack_name: the name of the stack
    :param package: the name of the package, as-used by <conf-selector-tool>
    :param version: the version number to create
    :param try_create: optional argument to attempt to create the directory before setting it
    :param ignore_errors: optional argument to ignore any error and simply log a warning
    """
    try:
      # do nothing if the stack does not support versioned configurations
      if not _valid(stack_name, package, version):
        return
  
      if try_create:
        create(stack_name, package, version)
  
      shell.checked_call(_get_cmd("set-conf-dir", package, version), logoutput=False, quiet=False, sudo=True)
  
      # for consistency sake, we must ensure that the /etc/<component>/conf symlink exists and
      # points to <stack-root>/current/<component>/conf - this is because some people still prefer to
      # use /etc/<component>/conf even though <stack-root> is the "future"
      package_dirs = get_package_dirs()
      if package in package_dirs:
        Logger.info("Ensuring that {0} has the correct symlink structure".format(package))
  
        directory_list = package_dirs[package]
        for directory_structure in directory_list:
          conf_dir = directory_structure["conf_dir"]
          current_dir = directory_structure["current_dir"]
  
          # if /etc/<component>/conf is missing or is not a symlink
          if not os.path.islink(conf_dir):
            # if /etc/<component>/conf is not a link and it exists, convert it to a symlink
            if os.path.exists(conf_dir):
              parent_directory = os.path.dirname(conf_dir)
              conf_backup_dir = os.path.join(parent_directory, "conf.backup")
  
              # create conf.backup and copy files to it (if it doesn't exist)
              Execute(("cp", "-R", "-p", conf_dir, conf_backup_dir),
                not_if = format("test -e {conf_backup_dir}"), sudo = True)
  
              # delete the old /etc/<component>/conf directory and link to the backup
              Directory(conf_dir, action="delete")
              Link(conf_dir, to = conf_backup_dir)
            else:
              # missing entirely
              # /etc/<component>/conf -> <stack-root>/current/<component>/conf
              if package in ["atlas", ]:
                #HACK for Atlas
                '''
                In the case of Atlas, the Hive RPM installs /usr/$stack/$version/atlas with some partial packages that
                contain Hive hooks, while the Atlas RPM is responsible for installing the full content.
  
                If the user does not have Atlas currently installed on their stack, then /usr/$stack/current/atlas-client
                will be a broken symlink, and we should not create the
                symlink /etc/atlas/conf -> /usr/$stack/current/atlas-client/conf .
                If we mistakenly create this symlink, then when the user performs an EU/RU and then adds Atlas service
                then the Atlas RPM will not be able to copy its artifacts into /etc/atlas/conf directory and therefore
                prevent Ambari from by copying those unmanaged contents into /etc/atlas/$version/0
                '''
                parent_dir = os.path.dirname(current_dir)
                if os.path.exists(parent_dir):
                  Link(conf_dir, to=current_dir)
                else:
                  Logger.info("Will not create symlink from {0} to {1} because the destination's parent dir does not exist.".format(conf_dir, current_dir))
              else:
                # Normal path for other packages
                Link(conf_dir, to=current_dir)
  
    except Exception, exception:
      if ignore_errors is True:
        Logger.warning("Could not select the directory for package {0}. Error: {1}".format(package,
          str(exception)))
      else:
        raise
  
  
  
  def get_hadoop_conf_dir(force_latest_on_upgrade=False):
    """
    Gets the shared hadoop conf directory using:
    1.  Start with /etc/hadoop/conf
    2.  When the stack is greater than HDP-2.2, use <stack-root>/current/hadoop-client/conf
    3.  Only when doing a RU and HDP-2.3 or higher, use the value as computed
        by <conf-selector-tool>.  This is in the form <stack-root>/VERSION/hadoop/conf to make sure
        the configs are written in the correct place. However, if the component itself has
        not yet been upgraded, it should use the hadoop configs from the prior version.
        This will perform an <stack-selector-tool> status to determine which version to use.
    :param force_latest_on_upgrade:  if True, then force the returned path to always
    be that of the upgrade target version, even if <stack-selector-tool> has not been called. This
    is primarily used by hooks like before-ANY to ensure that hadoop environment
    configurations are written to the correct location since they are written out
    before the <stack-selector-tool>/<conf-selector-tool> would have been called.
    """
    hadoop_conf_dir = "/etc/hadoop/conf"
    stack_name = None
    stack_root = Script.get_stack_root()
    stack_version = Script.get_stack_version()
    version = None
    allow_setting_conf_select_symlink = False
  
    if not Script.in_stack_upgrade():
      # During normal operation, the HDP stack must be 2.3 or higher
      if stack_version and check_stack_feature(StackFeature.ROLLING_UPGRADE, stack_version):
        hadoop_conf_dir = os.path.join(stack_root, "current", "hadoop-client", "conf")
  
      if stack_version and check_stack_feature(StackFeature.CONFIG_VERSIONING, stack_version):
        hadoop_conf_dir = os.path.join(stack_root, "current", "hadoop-client", "conf")
        stack_name = default("/hostLevelParams/stack_name", None)
        version = default("/commandParams/version", None)
  
        if not os.path.islink(hadoop_conf_dir) and stack_name and version:
          version = str(version)
          allow_setting_conf_select_symlink = True
    else:
      # During an upgrade/downgrade, which can be a Rolling or Express Upgrade, need to calculate it based on the version
      '''
      Whenever upgrading to HDP 2.2, or downgrading back to 2.2, need to use /etc/hadoop/conf
      Whenever upgrading to HDP 2.3, or downgrading back to 2.3, need to use a versioned hadoop conf dir
  
      Type__|_Source_|_Target_|_Direction_____________|_Comment_____________________________________________________________
      Normal|        | 2.2    |                       | Use /etc/hadoop/conf
      Normal|        | 2.3    |                       | Use /etc/hadoop/conf, which should be a symlink to <stack-root>/current/hadoop-client/conf
      EU    | 2.1    | 2.3    | Upgrade               | Use versioned <stack-root>/current/hadoop-client/conf
            |        |        | No Downgrade Allowed  | Invalid
      EU/RU | 2.2    | 2.2.*  | Any                   | Use <stack-root>/current/hadoop-client/conf
      EU/RU | 2.2    | 2.3    | Upgrade               | Use <stack-root>/$version/hadoop/conf, which should be a symlink destination
            |        |        | Downgrade             | Use <stack-root>/current/hadoop-client/conf
      EU/RU | 2.3    | 2.3.*  | Any                   | Use <stack-root>/$version/hadoop/conf, which should be a symlink destination
      '''
  
      # The "stack_version" is the desired stack, e.g., 2.2 or 2.3
      # In an RU, it is always the desired stack, and doesn't change even during the Downgrade!
      # In an RU Downgrade from HDP 2.3 to 2.2, the first thing we do is 
      # rm /etc/[component]/conf and then mv /etc/[component]/conf.backup /etc/[component]/conf
      if stack_version and check_stack_feature(StackFeature.ROLLING_UPGRADE, stack_version):
        hadoop_conf_dir = os.path.join(stack_root, "current", "hadoop-client", "conf")
  
        # This contains the "version", including the build number, that is actually used during a stack upgrade and
        # is the version upgrading/downgrading to.
        stack_info = stack_select._get_upgrade_stack()
  
        if stack_info is not None:
          stack_name = stack_info[0]
          version = stack_info[1]
        else:
          raise Fail("Unable to get parameter 'version'")
        
        Logger.info("In the middle of a stack upgrade/downgrade for Stack {0} and destination version {1}, determining which hadoop conf dir to use.".format(stack_name, version))
        # This is the version either upgrading or downgrading to.
        if version and check_stack_feature(StackFeature.CONFIG_VERSIONING, version):
          # Determine if <stack-selector-tool> has been run and if not, then use the current
          # hdp version until this component is upgraded.
          if not force_latest_on_upgrade:
            current_stack_version = stack_select.get_role_component_current_stack_version()
            if current_stack_version is not None and version != current_stack_version:
              version = current_stack_version
              stack_selector_name = stack_tools.get_stack_tool_name(stack_tools.STACK_SELECTOR_NAME)
              Logger.info("{0} has not yet been called to update the symlink for this component, "
                          "keep using version {1}".format(stack_selector_name, current_stack_version))
  
          # Only change the hadoop_conf_dir path, don't <conf-selector-tool> this older version
          hadoop_conf_dir = os.path.join(stack_root, version, "hadoop", "conf")
          Logger.info("Hadoop conf dir: {0}".format(hadoop_conf_dir))
  
          allow_setting_conf_select_symlink = True
  
    if allow_setting_conf_select_symlink:
      # If not in the middle of an upgrade and on HDP 2.3 or higher, or if
      # upgrading stack to version 2.3.0.0 or higher (which may be upgrade or downgrade), then consider setting the
      # symlink for /etc/hadoop/conf.
      # If a host does not have any HDFS or YARN components (e.g., only ZK), then it will not contain /etc/hadoop/conf
      # Therefore, any calls to <conf-selector-tool> will fail.
      # For that reason, if the hadoop conf directory exists, then make sure it is set.
      if os.path.exists(hadoop_conf_dir):
        conf_selector_name = stack_tools.get_stack_tool_name(stack_tools.CONF_SELECTOR_NAME)
        Logger.info("The hadoop conf dir {0} exists, will call {1} on it for version {2}".format(
                hadoop_conf_dir, conf_selector_name, version))
        select(stack_name, "hadoop", version)
  
    Logger.info("Using hadoop conf dir: {0}".format(hadoop_conf_dir))
    return hadoop_conf_dir
  
  
  def convert_conf_directories_to_symlinks(package, version, dirs, skip_existing_links=True,
      link_to=DIRECTORY_TYPE_CURRENT):
    """
    Assumes HDP 2.3+, moves around directories and creates the conf symlink for the given package.
    If the package does not exist, then no work is performed.
  
    - Creates a /etc/<component>/conf.backup directory
    - Copies all configs from /etc/<component>/conf to conf.backup
    - Removes /etc/<component>/conf
    - Creates /etc/<component>/<version>/0 via <conf-selector-tool>
    - <stack-root>/current/<component>-client/conf -> /etc/<component>/<version>/0 via <conf-selector-tool>
    - Links /etc/<component>/conf to <something> depending on function paramter
    -- /etc/<component>/conf -> <stack-root>/current/[component]-client/conf (usually)
    -- /etc/<component>/conf -> /etc/<component>/conf.backup (only when supporting < HDP 2.3)
  
    :param package: the package to create symlinks for (zookeeper, falcon, etc)
    :param version: the version number to use with <conf-selector-tool> (2.3.0.0-1234)
    :param dirs: the directories associated with the package (from get_package_dirs())
    :param skip_existing_links: True to not do any work if already a symlink
    :param link_to: link to "current" or "backup"
    """
    # lack of enums makes this possible - we need to know what to link to
    if link_to not in [DIRECTORY_TYPE_CURRENT, DIRECTORY_TYPE_BACKUP]:
      raise Fail("Unsupported 'link_to' argument. Could not link package {0}".format(package))
  
    stack_name = Script.get_stack_name()
    bad_dirs = []
    for dir_def in dirs:
      if not os.path.exists(dir_def['conf_dir']):
        bad_dirs.append(dir_def['conf_dir'])
  
    if len(bad_dirs) > 0:
      Logger.info("Skipping {0} as it does not exist.".format(",".join(bad_dirs)))
      return
  
    # existing links should be skipped since we assume there's no work to do
    # they should be checked against the correct target though
    if skip_existing_links:
      bad_dirs = []
      for dir_def in dirs:
        # check if conf is a link already
        old_conf = dir_def['conf_dir']
        if os.path.islink(old_conf):
          # it's already a link; make sure it's a link to where we want it
          if link_to == DIRECTORY_TYPE_BACKUP:
            target_conf_dir = _get_backup_conf_directory(old_conf)
          else:
            target_conf_dir = dir_def['current_dir']
  
          # the link isn't to the right spot; re-link it
          if os.readlink(old_conf) != target_conf_dir:
            Logger.info("Re-linking symlink {0} to {1}".format(old_conf, target_conf_dir))
  
            Link(old_conf, action = "delete")
            Link(old_conf, to = target_conf_dir)
          else:
            Logger.info("{0} is already linked to {1}".format(old_conf, os.path.realpath(old_conf)))
  
          bad_dirs.append(old_conf)
  
    if len(bad_dirs) > 0:
      return
  
    # make backup dir and copy everything in case configure() was called after install()
    for dir_def in dirs:
      old_conf = dir_def['conf_dir']
      backup_dir = _get_backup_conf_directory(old_conf)
      Logger.info("Backing up {0} to {1} if destination doesn't exist already.".format(old_conf, backup_dir))
      Execute(("cp", "-R", "-p", old_conf, backup_dir),
        not_if = format("test -e {backup_dir}"), sudo = True)
  
    # we're already in the HDP stack
    # Create the versioned /etc/[component]/[version]/0 folder.
    # The component must be installed on the host.
    versioned_confs = create(stack_name, package, version, dry_run = True)
  
    Logger.info("Package {0} will have new conf directories: {1}".format(package, ", ".join(versioned_confs)))
  
    need_dirs = []
    for d in versioned_confs:
      if not os.path.exists(d):
        need_dirs.append(d)
  
    if len(need_dirs) > 0:
      create(stack_name, package, version)
  
      # find the matching definition and back it up (not the most efficient way) ONLY if there is more than one directory
      if len(dirs) > 1:
        for need_dir in need_dirs:
          for dir_def in dirs:
            if 'prefix' in dir_def and need_dir.startswith(dir_def['prefix']):
              old_conf = dir_def['conf_dir']
              versioned_conf = need_dir
              Execute(as_sudo(["cp", "-R", "-p", os.path.join(old_conf, "*"), versioned_conf], auto_escape=False),
                only_if = format("ls -d {old_conf}/*"))
      elif 1 == len(dirs) and 1 == len(need_dirs):
        old_conf = dirs[0]['conf_dir']
        versioned_conf = need_dirs[0]
        Execute(as_sudo(["cp", "-R", "-p", os.path.join(old_conf, "*"), versioned_conf], auto_escape=False),
          only_if = format("ls -d {old_conf}/*"))
  
  
    # <stack-root>/current/[component] is already set to to the correct version, e.g., <stack-root>/[version]/[component]
    
    select(stack_name, package, version, ignore_errors = True)
  
    # Symlink /etc/[component]/conf to /etc/[component]/conf.backup
    try:
      # No more references to /etc/[component]/conf
      for dir_def in dirs:
        # E.g., /etc/[component]/conf
        new_symlink = dir_def['conf_dir']
  
        # Delete the existing directory/link so that linking will work
        if not os.path.islink(new_symlink):
          Directory(new_symlink, action = "delete")
        else:
          Link(new_symlink, action = "delete")
  
        old_conf = dir_def['conf_dir']
        backup_dir = _get_backup_conf_directory(old_conf)
        # link /etc/[component]/conf -> /etc/[component]/conf.backup
        # or
        # link /etc/[component]/conf -> <stack-root>/current/[component]-client/conf
        if link_to == DIRECTORY_TYPE_BACKUP:
          Link(new_symlink, to=backup_dir)
        else:
          Link(new_symlink, to=dir_def['current_dir'])
  
          #HACK
          if package in ["atlas", ]:
            Logger.info("Seeding the new conf symlink {0} from the old backup directory {1} in case any "
                        "unmanaged artifacts are needed.".format(new_symlink, backup_dir))
            # If /etc/[component]/conf.backup exists, then copy any artifacts not managed by Ambari to the new symlink target
            # Be careful not to clobber any existing files.
            Execute(as_sudo(["cp", "-R", "--no-clobber", os.path.join(backup_dir, "*"), new_symlink], auto_escape=False),
                    only_if=format("test -e {new_symlink}"))
    except Exception, e:
      Logger.warning("Could not change symlink for package {0} to point to {1} directory. Error: {2}".format(package, link_to, e))
  
  
  def _seed_new_configuration_directories(package, created_directories):
    """
    Copies any files from the "current" configuration directory to the directories which were
    newly created with <conf-selector-tool>. This function helps ensure that files which are not tracked
    by Ambari will be available after performing a stack upgrade. Although old configurations
    will be copied as well, they will be overwritten when the components are writing out their
    configs after upgrade during their restart.
  
    This function will catch all errors, logging them, but not raising an exception. This is to
    prevent problems here from stopping and otherwise healthy upgrade.
  
    :param package: the <conf-selector-tool> package name
    :param created_directories: a list of directories that <conf-selector-tool> said it created
    :return: None
    """
    package_dirs = get_package_dirs()
    if package not in package_dirs:
      Logger.warning("Unable to seed newly created configuration directories for {0} because it is an unknown component".format(package))
      return
  
    # seed the directories with any existing configurations
    # this allows files which are not tracked by Ambari to be available after an upgrade
    Logger.info("Seeding versioned configuration directories for {0}".format(package))
    expected_directories = package_dirs[package]
  
    try:
      # if the expected directories don't match those created, we can't seed them
      if len(created_directories) != len(expected_directories):
        Logger.warning("The known configuration directories for {0} do not match those created by conf-select: {1}".format(
          package, str(created_directories)))
  
        return
  
      # short circuit for a simple 1:1 mapping
      if len(expected_directories) == 1:
        # <stack-root>/current/component/conf
        # the current directory is the source of the seeded configurations;
        source_seed_directory = expected_directories[0]["current_dir"]
        target_seed_directory = created_directories[0]
        _copy_configurations(source_seed_directory, target_seed_directory)
      else:
        for created_directory in created_directories:
          for expected_directory_structure in expected_directories:
            prefix = expected_directory_structure.get("prefix", None)
            if prefix is not None and created_directory.startswith(prefix):
              source_seed_directory = expected_directory_structure["current_dir"]
              target_seed_directory = created_directory
              _copy_configurations(source_seed_directory, target_seed_directory)
  
    except Exception, e:
      Logger.warning("Unable to seed new configuration directories for {0}. {1}".format(package, str(e)))
  
  
  def _copy_configurations(source_directory, target_directory):
    """
    Copies from the source directory to the target directory. If the source directory is a symlink
    then it will be followed (deferenced) but any other symlinks found to copy will not be. This
    will ensure that if the configuration directory itself is a symlink, then it's contents will be
    copied, preserving and children found which are also symlinks.
  
    :param source_directory:  the source directory to copy from
    :param target_directory:  the target directory to copy to
    :return: None
    """
    # append trailing slash so the cp command works correctly WRT recursion and symlinks
    source_directory = os.path.join(source_directory, "*")
    Execute(as_sudo(["cp", "-R", "-p", "-v", source_directory, target_directory], auto_escape = False),
      logoutput = True)
  
  def _get_backup_conf_directory(old_conf):
    """
    Calculates the conf.backup absolute directory given the /etc/<component>/conf location.
    :param old_conf:  the old conf directory (ie /etc/<component>/conf)
    :return:  the conf.backup absolute directory
    """
    old_parent = os.path.abspath(os.path.join(old_conf, os.pardir))
    backup_dir = os.path.join(old_parent, "conf.backup")
    return backup_dir
  