def add_sudo(cmd):
    if config.core.user:
        return "sudo -E -u %s %s" % (config.core.user, cmd)
    return cmd def str2file(s, fname, mod=0o644):
    '''
    Write a string to a file.
    '''
    try:
        with open_atomic(fname, 'w', encoding='utf-8', fsync=True) as dst:
            dst.write(to_ascii(s))
        os.chmod(fname, mod)
    except IOError as msg:
        logger.error(msg)
        return False
    return True def str2tmp(_str, suffix=".pcmk"):
    '''
    Write the given string to a temporary file. Return the name
    of the file.
    '''
    s = to_ascii(_str)
    fd, tmp = mkstemp(suffix=suffix)
    try:
        f = os.fdopen(fd, "w")
    except IOError as msg:
        logger.error(msg)
        return
    f.write(s)
    if not s.endswith('\n'):
        f.write("\n")
    f.close()
    return tmp def get_boolean(opt, dflt=False):
    if not opt:
        return dflt
    return is_boolean_true(opt) def handle_role_for_ocf_1_1(value, name='role'):
    """
    * Convert role from Promoted/Unpromoted to Master/Slave if schema doesn't support OCF 1.1
    * Convert role from Master/Slave to Promoted/Unpromoted if ocf1.1 cib schema detected and OCF_1_1_SUPPORT is yes
    """
    role_names = ["role", "target-role"]
    downgrade_dict = {"Promoted": "Master", "Unpromoted": "Slave"}
    upgrade_dict = {v: k for k, v in downgrade_dict.items()}

    if name not in role_names:
        return value
    if value in downgrade_dict and not is_ocf_1_1_cib_schema_detected():
        logger.warning('Convert "%s" to "%s" since the current schema version is old and not upgraded yet. Please consider "%s"', value, downgrade_dict[value], constants.CIB_UPGRADE)
        return downgrade_dict[value]
    if value in upgrade_dict and is_ocf_1_1_cib_schema_detected() and config.core.OCF_1_1_SUPPORT:
        logger.info('Convert deprecated "%s" to "%s"', value, upgrade_dict[value])
        return upgrade_dict[value]

    return value def get_stdout(cmd, input_s=None, stderr_on=True, shell=True, raw=False):
    '''
    Run a cmd, return stdout output.
    Optional input string "input_s".
    stderr_on controls whether to show output which comes on stderr.
    '''
    if stderr_on:
        stderr = None
    else:
        stderr = subprocess.PIPE
    if options.regression_tests:
        print(".EXT", cmd)
    proc = subprocess.Popen(cmd,
                            shell=shell,
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=stderr)
    stdout_data, stderr_data = proc.communicate(input_s)
    if raw:
        return proc.returncode, stdout_data
    return proc.returncode, to_ascii(stdout_data).strip() def get_stdout_or_raise_error(cmd, remote=None, success_val_list=[0], no_raise=False):
    """
    Common function to get stdout from cmd or raise exception
    """
    if remote:
        cmd = "ssh {} root@{} \"{}\"".format(SSH_OPTION, remote, cmd)
    rc, out, err = get_stdout_stderr(cmd, no_reg=True)
    if rc not in success_val_list and not no_raise:
        raise ValueError("Failed to run \"{}\": {}".format(cmd, err))
    return out def stdout2list(cmd, stderr_on=True, shell=True):
    '''
    Run a cmd, fetch output, return it as a list of lines.
    stderr_on controls whether to show output which comes on stderr.
    '''
    rc, s = get_stdout(add_sudo(cmd), stderr_on=stderr_on, shell=shell)
    if not s:
        return rc, []
    return rc, s.split('\n') def crm_msec(t):
    '''
    See lib/common/utils.c:crm_get_msec().
    '''
    convtab = {
        'ms': (1, 1),
        'msec': (1, 1),
        'us': (1, 1000),
        'usec': (1, 1000),
        '': (1000, 1),
        's': (1000, 1),
        'sec': (1000, 1),
        'm': (60*1000, 1),
        'min': (60*1000, 1),
        'h': (60*60*1000, 1),
        'hr': (60*60*1000, 1),
    }
    if not t:
        return -1
    r = re.match(r"\s*(\d+)\s*([a-zA-Z]+)?", t)
    if not r:
        return -1
    if not r.group(2):
        q = ''
    else:
        q = r.group(2).lower()
    try:
        mult, div = convtab[q]
    except KeyError:
        return -1
    return (int(r.group(1))*mult) // div def crm_time_cmp(a, b):
    return crm_msec(a) - crm_msec(b) class olist(list):
    """
    Implements the 'in' operator
    in a case-insensitive manner,
    allowing "if x in olist(...)"
    """
    def __init__(self, keys):
        super(olist, self).__init__([k.lower() for k in keys])

    def __contains__(self, key):
        return super(olist, self).__contains__(key.lower())

    def append(self, key):
        super(olist, self).append(key.lower()) def get_cib_in_use():
    return _cib_in_use def get_tempdir():
    return os.getenv("TMPDIR") or "/tmp" def to_ascii(input_str):
    """Convert the bytes string to a ASCII string
    Usefull to remove accent (diacritics)"""
    if input_str is None:
        return input_str
    if isinstance(input_str, str):
        return input_str
    try:
        return str(input_str, 'utf-8')
    except UnicodeDecodeError:
        if config.core.debug or options.regression_tests:
            import traceback
            traceback.print_exc()
        return input_str.decode('utf-8', errors='ignore') def is_boolean_true(opt):
    if opt in (None, False):
        return False
    if opt is True:
        return True
    return opt.lower() in ("yes", "true", "on", "1")
import os
import subprocess
import copy
import bz2
            import gzip
from lxml import etree, doctestcompare
from collections import defaultdict
from . import config
from . import options
from . import schema
from . import constants
from . import userdir
from .utils import add_sudo, str2file, str2tmp, get_boolean, handle_role_for_ocf_1_1
from .utils import get_stdout, get_stdout_or_raise_error, stdout2list, crm_msec, crm_time_cmp
from .utils import olist, get_cib_in_use, get_tempdir, to_ascii, is_boolean_true
from . import log
        from . import idmgmt
        from . import idmgmt
        from . import idmgmt
        from doctest import Example

    def is_running(self, ident):
        '''
        Is this resource running?
        '''
        if not is_live_cib():
            return False
        test_id = self.rsc_clone(ident) or ident
        rc, outp = get_stdout(self.rsc_status % test_id, stderr_on=False)
        return outp.find("running") > 0 and outp.find("NOT") == -1

    def is_group(self, ident):
        '''
        Test if the resource is a group
        '''
        rsc_node = self.rsc2node(ident)
        if rsc_node is None:
            return False
        return is_group(rsc_node)

    def can_delete(self, ident):
        '''
        Can a resource be deleted?
        The order below is important!
        '''
        return not (self.is_running(ident) and not self.is_group(ident) and self.is_managed(ident))


def resources_xml():
    return cibdump2elem("resources")


def is_normal_node(n):
    return n.tag == "node" and (n.get("type") in (None, "normal", "member", ""))


def unique_ra(typ, klass, provider):
    """
    Unique:
    * it's explicitly ocf:heartbeat:
    * no explicit class or provider
    * only one provider (heartbeat counts as one provider)
    Not unique:
    * class is not ocf
    * multiple providers
    """
    if klass is None and provider is None:
        return True
    return klass == 'ocf' and provider is None or provider == 'heartbeat'


def mk_rsc_type(n):
    """
    Returns prefixless for unique RAs
    """
    ra_type = n.get("type")
    ra_class = n.get("class")
    ra_provider = n.get("provider")
    if unique_ra(ra_type, ra_class, ra_provider):
        ra_class = None
        ra_provider = None
    s1 = s2 = ''
    if ra_class:
        s1 = "%s:" % ra_class
    if ra_provider:
        s2 = "%s:" % ra_provider
    return ''.join((s1, s2, ra_type))


def listnodes(include_remote_nodes=True):
    cib = cibdump2elem()
    if cib is None:
        return []
    local_nodes = cib.xpath('/cib/configuration/nodes/node/@uname')
    if include_remote_nodes:
        remote_nodes = cib.xpath('/cib/status/node_state[@remote_node="true"]/@uname')
    else:
        remote_nodes = []
    return list(set([n for n in local_nodes + remote_nodes if n]))


def is_our_node(s):
    '''
    Check if s is in a list of our nodes (ignore case).
    This is not fast, perhaps should be cached.

    Includes remote nodes as well
    '''
    for n in listnodes():
        if n.lower() == s.lower():
            return True
    return False


def is_remote_node(n):
    cib = cibdump2elem()
    if cib is None:
        return False
    remote_nodes = cib.xpath('/cib/status/node_state[@remote_node="true"]/@uname')
    return any(n == r for r in remote_nodes if r)


def is_live_cib():
    '''We working with the live cluster?'''
    return not get_cib_in_use() and not os.getenv("CIB_file")


def is_crmuser():
    crmusers = ("root", config.path.crm_daemon_user)
    return config.core.user in crmusers or userdir.getuser() in crmusers


def cib_shadow_dir():
    if os.getenv("CIB_shadow_dir"):
        return os.getenv("CIB_shadow_dir")
    if is_crmuser():
        return config.path.crm_config
    home = userdir.gethomedir(config.core.user)
    if home and home.startswith(os.path.sep):
        return os.path.join(home, ".cib")

    return get_tempdir()