diff --git a/tools/crm.in b/tools/crm.in
deleted file mode 100644
index f4dbf37f6d..0000000000
--- a/tools/crm.in
+++ /dev/null
@@ -1,7622 +0,0 @@
-#!/usr/bin/python
-#
-
-# Copyright (C) 2008 Dejan Muhamedagic <dmuhamedagic@suse.de>
-# 
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-# 
-# This software is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-
-import shlex
-import os
-from tempfile import mkstemp
-import subprocess
-import sys
-import time
-import readline
-import copy
-import xml.dom.minidom
-import signal
-import re
-import glob
-
-def is_program(prog):
-    return subprocess.call("which %s >/dev/null 2>&1"%prog, shell=True) == 0
-def prereqs():
-    proglist = "which cibadmin crm_resource crm_attribute crm_mon crm_standby crm_failcount"
-    for prog in proglist.split():
-        if not is_program(prog):
-            print >> sys.stderr, "%s not available, check your installation"%prog
-            sys.exit(1)
-prereqs()
-
-lineno = -1
-regression_tests = False
-
-class ErrorBuffer(object):
-    '''
-    Show error messages either immediately or buffered.
-    '''
-    def __init__(self):
-        self.msg_list = []
-        self.mode = "immediate"
-    def buffer(self):
-        self.mode = "keep"
-    def release(self):
-        if self.msg_list:
-            print >> sys.stderr, '\n'.join(self.msg_list)
-            if not batch:
-                try:
-                    raw_input("Press enter to continue... ")
-                except EOFError:
-                    pass
-            self.msg_list = []
-        self.mode = "immediate"
-    def writemsg(self,msg):
-        if self.mode == "immediate":
-            if regression_tests:
-                print msg
-            else:
-                print >> sys.stderr, msg
-        else:
-            self.msg_list.append(msg)
-    def error(self,s):
-        self.writemsg("ERROR: %s" % add_lineno(s))
-    def warning(self,s):
-        self.writemsg("WARNING: %s" % add_lineno(s))
-    def info(self,s):
-        self.writemsg("INFO: %s" % add_lineno(s))
-    def debug(self,s):
-        if user_prefs.get_debug():
-            self.writemsg("DEBUG: %s" % add_lineno(s))
-
-err_buf = ErrorBuffer()
-
-def add_lineno(s):
-    if lineno > 0:
-        return "%d: %s" % (lineno,s)
-    else: return s
-
-def common_err(s):
-    err_buf.error(s)
-def common_warn(s):
-    err_buf.warning(s)
-def common_info(s):
-    err_buf.info(s)
-def common_debug(s):
-    err_buf.debug(s)
-def no_prog_err(name):
-    err_buf.error("%s not available, check your installation"%name)
-def missing_prog_warn(name):
-    err_buf.warning("could not find any %s on the system"%name)
-def no_attribute_err(attr,obj_type):
-    err_buf.error("required attribute %s not found in %s"%(attr,obj_type))
-def bad_def_err(what,msg):
-    err_buf.error("bad %s definition: %s"%(what,msg))
-def unsupported_err(name):
-    err_buf.error("%s is not supported"%name)
-def no_such_obj_err(name):
-    err_buf.error("%s object is not supported"%name)
-def obj_cli_err(name):
-    err_buf.error("object %s cannot be represented in the CLI notation"%name)
-def missing_obj_err(node):
-    err_buf.error("object %s:%s missing (shouldn't have happened)"% \
-        (node.tagName,node.getAttribute("id")))
-def constraint_norefobj_err(constraint_id,obj_id):
-    err_buf.error("constraint %s references a resource %s which doesn't exist"% \
-        (constraint_id,obj_id))
-def obj_exists_err(name):
-    err_buf.error("object %s already exists"%name)
-def no_object_err(name):
-    err_buf.error("object %s does not exist"%name)
-def invalid_id_err(obj_id):
-    err_buf.error("%s: invalid object id"%obj_id)
-def id_used_err(node_id):
-    err_buf.error("%s: id is already in use"%node_id)
-def skill_err(s):
-    err_buf.error("%s: this command is not allowed at this skill level"%' '.join(s))
-def syntax_err(s,token = '',context = ''):
-    pfx = "syntax"
-    if context:
-        pfx = "%s in %s" %(pfx,context)
-    if type(s) == type(''):
-        err_buf.error("%s near <%s>"%(pfx,s))
-    elif token:
-        err_buf.error("%s near <%s>: %s"%(pfx,token,' '.join(s)))
-    else:
-        err_buf.error("%s: %s"%(pfx,' '.join(s)))
-def bad_usage(cmd,args):
-    err_buf.error("bad usage: %s %s"%(cmd,args))
-def empty_cib_err():
-    err_buf.error("No CIB!")
-def cib_parse_err(msg):
-    err_buf.error("%s"%msg)
-def cib_no_elem_err(el_name):
-    err_buf.error("CIB contains no '%s' element!"%el_name)
-def cib_ver_unsupported_err(validator,rel):
-    err_buf.error("CIB not supported: validator '%s', release '%s'"% (validator,rel))
-    err_buf.error("You may try the upgrade command")
-def update_err(obj_id,cibadm_opt,xml):
-    if cibadm_opt == '-U':
-        task = "update"
-    elif cibadm_opt == '-D':
-        task = "delete"
-    else:
-        task = "replace"
-    err_buf.error("could not %s %s"%(task,obj_id))
-    err_buf.info("offending xml: %s" % xml)
-def not_impl_info(s):
-    err_buf.info("%s is not implemented yet" % s)
-
-def ask(msg):
-    print_msg = True
-    while True:
-        try:
-            ans = raw_input(msg + ' ')
-        except EOFError:
-            ans = 'n'
-        if not ans or ans[0].lower() not in ('n','y'):
-            if print_msg:
-                print "Please answer with y[es] or n[o]"
-                print_msg = False
-        else:
-            return ans[0].lower() == 'y'
-
-def keyword_cmp(string1, string2):
-    return string1.lower() == string2.lower()
-
-from UserDict import DictMixin
-class odict(DictMixin):
-    def __init__(self, data=None, **kwdata):
-        self._keys = []
-        self._data = {}
-    def __setitem__(self, key, value):
-        if key not in self._data:
-            self._keys.append(key)
-        self._data[key] = value
-    def __getitem__(self, key):
-        if key not in self._data:
-            return self._data[key.lower()]
-        return self._data[key]
-    def __delitem__(self, key):
-        del self._data[key]
-        self._keys.remove(key)
-    def keys(self):
-        return list(self._keys)
-    def copy(self):
-        copyDict = odict()
-        copyDict._data = self._data.copy()
-        copyDict._keys = self._keys[:]
-        return copyDict
-
-class olist(list):
-    def __init__(self, keys):
-        #print "Init %s" % (repr(keys))
-        super(olist, self).__init__()
-        for key in keys:
-            self.append(key)
-            self.append(key.upper())
-
-def help_short(s):
-    r = re.search("help_[^,]+,(.*)\]\]", s)
-    return r and r.group(1) or ''
-class HelpSystem(object):
-    '''
-    The help system. All help is in the following form in the
-    manual:
-    [[cmdhelp_<level>_<cmd>,<short help text>]]
-    === ...
-    Long help text.
-    ...
-    [[cmdhelp_<level>_<cmd>,<short help text>]]
-
-    Help for the level itself is like this:
-
-    [[cmdhelp_<level>,<short help text>]]
-    '''
-    help_text_file = "@datadir@/@PACKAGE@/crm_cli.txt"
-    index_file = "%s/%s" % (os.getenv("HOME"),".crm_help_index")
-    def __init__(self):
-        self.key_pos = {}
-        self.key_list = []
-        self.no_help_file = False  # don't print repeatedly messages
-        self.bad_index = False  # don't print repeatedly warnings for bad index
-    def open_file(self,name,mode):
-        try:
-            f = open(name,mode)
-            return f
-        except IOError,msg:
-            common_err("%s open: %s"%(name,msg))
-            common_err("extensive help system is not available")
-            self.no_help_file = True
-            return None
-    def drop_index(self):
-        common_info("removing index")
-        os.unlink(self.index_file)
-        self.key_pos = {}
-        self.key_list = []
-        self.bad_index = True
-    def mk_index(self):
-        '''
-        Prepare an index file, sorted by topic, with seek positions
-        Do we need a hash on content?
-        '''
-        if self.no_help_file:
-            return False
-        crm_help_v = os.getenv("CRM_HELP_FILE")
-        if crm_help_v:
-            self.help_text_file = crm_help_v
-        help_f = self.open_file(self.help_text_file,"r")
-        if not help_f:
-            return False
-        idx_f = self.open_file(self.index_file,"w")
-        if not idx_f:
-            return False
-        common_info("building help index")
-        key_pos = {}
-        while 1:
-            pos = help_f.tell()
-            s = help_f.readline()
-            if not s:
-                break
-            if s.startswith("[["):
-                r = re.search(r'..([^,]+),', s)
-                if r:
-                    key_pos[r.group(1)] = pos
-        help_f.close()
-        l = key_pos.keys()
-        l.sort()
-        for key in l:
-            print >>idx_f, '%s %d' % (key,key_pos[key])
-        idx_f.close()
-        return True
-    def is_index_old(self):
-        try:
-            t_idx = os.path.getmtime(self.index_file)
-        except:
-            return True
-        try:
-            t_help = os.path.getmtime(self.help_text_file)
-        except:
-            return True
-        return t_help > t_idx
-    def load_index(self):
-        if self.is_index_old():
-            self.mk_index()
-        self.key_pos = {}
-        idx_f = self.open_file(self.index_file,"r")
-        if not idx_f:
-            return False
-        for s in idx_f:
-            a = s.split()
-            if len(a) != 2:
-                if not self.bad_index:
-                    common_err("index file corrupt")
-                    idx_f.close()
-                    self.drop_index()
-                    return self.load_index() # this runs only once
-                return False
-            self.key_pos[a[0]] = long(a[1])
-        idx_f.close()
-        self.key_list = self.key_pos.keys()
-        self.key_list.sort()
-        return True
-    def __filter(self,s):
-        if '<<' in s:
-            return re.sub(r'<<[^,]+,(.+)>>', r'\1', s)
-        else:
-            return s
-    def __find_key(self,key):
-        low = 0
-        high = len(self.key_list)-1
-        while low <= high:
-            mid = (low + high)/2
-            if self.key_list[mid] > key:
-                high = mid - 1
-            elif self.key_list[mid] < key:
-                low = mid + 1
-            else:
-                return mid
-        return -1
-    def __load_help_one(self,key,skip = 2):
-        longhelp = ''
-        self.help_f.seek(self.key_pos[key])
-        shorthelp = help_short(self.help_f.readline())
-        for i in range(skip-1):
-            self.help_f.readline()
-        l = []
-        for s in self.help_f:
-            if s.startswith("[[") or s.startswith("="):
-                break
-            l.append(self.__filter(s))
-        if l and l[-1] == '\n':  # drop the last line of empty
-            l.pop()
-        if l:
-            longhelp = ''.join(l)
-        if not shorthelp or not longhelp:
-            if not self.bad_index:
-                common_warn("help topic %s not found" % key)
-                self.drop_index()
-        return shorthelp,longhelp
-    def cmdhelp(self,s):
-        if not self.key_pos and not self.load_index():
-            return None,None
-        if not s in self.key_pos:
-            if not self.bad_index:
-                common_warn("help topic %s not found" % s)
-                self.drop_index()
-            return None,None
-        return self.__load_help_one(s)
-    def __load_level(self,lvl):
-        '''
-        For the given level, create a help table.
-        '''
-        if wcache.is_cached("lvl_help_tab_%s" % lvl):
-            return wcache.retrieve("lvl_help_tab_%s" % lvl)
-        if not self.key_pos and not self.load_index():
-            return None
-        self.help_f = self.open_file(self.help_text_file,"r")
-        if not self.help_f:
-            return None
-        lvl_s = "cmdhelp_%s" % lvl
-        if not lvl_s in self.key_pos:
-            if not self.bad_index:
-                common_warn("help table for level %s not found" % lvl)
-                self.drop_index()
-            return None
-        common_debug("loading help table for level %s" % lvl)
-        help_tab = odict()
-        help_tab["."] = self.__load_help_one(lvl_s)
-        lvl_idx = self.__find_key(lvl_s)
-        lvl_idx += 1
-        while lvl_idx < len(self.key_list):
-            key = self.key_list[lvl_idx]
-            if not key.startswith(lvl_s):
-                break
-            cmd = key[len(lvl_s)+1:]
-            help_tab[cmd] = self.__load_help_one(key)
-            lvl_idx += 1
-        self.help_f.close()
-        help_tab["quit"] = ("exit the program", "")
-        help_tab["help"] = ("show help", "")
-        help_tab["end"] = ("go back one level", "")
-        return help_tab
-    def load_level(self,lvl):
-        help_tab = self.__load_level(lvl)
-        if self.bad_index: # try again
-            help_tab = self.__load_level(lvl)
-        return wcache.store("lvl_help_tab_%s" % lvl, help_tab)
-
-# from: http://code.activestate.com/recipes/475116/
-
-class TerminalController(object):
-    """
-    A class that can be used to portably generate formatted output to
-    a terminal.  
-    
-    `TerminalController` defines a set of instance variables whose
-    values are initialized to the control sequence necessary to
-    perform a given action.  These can be simply included in normal
-    output to the terminal:
-
-        >>> term = TerminalController()
-        >>> print 'This is '+term.GREEN+'green'+term.NORMAL
-
-    Alternatively, the `render()` method can used, which replaces
-    '${action}' with the string required to perform 'action':
-
-        >>> term = TerminalController()
-        >>> print term.render('This is ${GREEN}green${NORMAL}')
-
-    If the terminal doesn't support a given action, then the value of
-    the corresponding instance variable will be set to ''.  As a
-    result, the above code will still work on terminals that do not
-    support color, except that their output will not be colored.
-    Also, this means that you can test whether the terminal supports a
-    given action by simply testing the truth value of the
-    corresponding instance variable:
-
-        >>> term = TerminalController()
-        >>> if term.CLEAR_SCREEN:
-        ...     print 'This terminal supports clearning the screen.'
-
-    Finally, if the width and height of the terminal are known, then
-    they will be stored in the `COLS` and `LINES` attributes.
-    """
-    # Cursor movement:
-    BOL = ''             #: Move the cursor to the beginning of the line
-    UP = ''              #: Move the cursor up one line
-    DOWN = ''            #: Move the cursor down one line
-    LEFT = ''            #: Move the cursor left one char
-    RIGHT = ''           #: Move the cursor right one char
-
-    # Deletion:
-    CLEAR_SCREEN = ''    #: Clear the screen and move to home position
-    CLEAR_EOL = ''       #: Clear to the end of the line.
-    CLEAR_BOL = ''       #: Clear to the beginning of the line.
-    CLEAR_EOS = ''       #: Clear to the end of the screen
-
-    # Output modes:
-    BOLD = ''            #: Turn on bold mode
-    BLINK = ''           #: Turn on blink mode
-    DIM = ''             #: Turn on half-bright mode
-    REVERSE = ''         #: Turn on reverse-video mode
-    NORMAL = ''          #: Turn off all modes
-
-    # Cursor display:
-    HIDE_CURSOR = ''     #: Make the cursor invisible
-    SHOW_CURSOR = ''     #: Make the cursor visible
-
-    # Terminal size:
-    COLS = None          #: Width of the terminal (None for unknown)
-    LINES = None         #: Height of the terminal (None for unknown)
-
-    # Foreground colors:
-    BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
-    
-    # Background colors:
-    BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
-    BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
-    
-    _STRING_CAPABILITIES = """
-    BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
-    CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
-    BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
-    HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
-    _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
-    _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
-
-    def __init__(self, term_stream=sys.stdout):
-        """
-        Create a `TerminalController` and initialize its attributes
-        with appropriate values for the current terminal.
-        `term_stream` is the stream that will be used for terminal
-        output; if this stream is not a tty, then the terminal is
-        assumed to be a dumb terminal (i.e., have no capabilities).
-        """
-        # Curses isn't available on all platforms
-        try: import curses
-        except:
-            common_info("no curses support: you won't see colors")
-            return
-
-        # If the stream isn't a tty, then assume it has no capabilities.
-        if not term_stream.isatty(): return
-
-        # Check the terminal type.  If we fail, then assume that the
-        # terminal has no capabilities.
-        try: curses.setupterm()
-        except: return
-
-        # Look up numeric capabilities.
-        self.COLS = curses.tigetnum('cols')
-        self.LINES = curses.tigetnum('lines')
-        
-        # Look up string capabilities.
-        for capability in self._STRING_CAPABILITIES:
-            (attrib, cap_name) = capability.split('=')
-            setattr(self, attrib, self._tigetstr(cap_name) or '')
-
-        # Colors
-        set_fg = self._tigetstr('setf')
-        if set_fg:
-            for i,color in zip(range(len(self._COLORS)), self._COLORS):
-                setattr(self, color, curses.tparm(set_fg, i) or '')
-        set_fg_ansi = self._tigetstr('setaf')
-        if set_fg_ansi:
-            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
-                setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
-        set_bg = self._tigetstr('setb')
-        if set_bg:
-            for i,color in zip(range(len(self._COLORS)), self._COLORS):
-                setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
-        set_bg_ansi = self._tigetstr('setab')
-        if set_bg_ansi:
-            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
-                setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
-
-    def _tigetstr(self, cap_name):
-        # String capabilities can include "delays" of the form "$<2>".
-        # For any modern terminal, we should be able to just ignore
-        # these, so strip them out.
-        import curses
-        cap = curses.tigetstr(cap_name) or ''
-        return re.sub(r'\$<\d+>[/*]?', '', cap)
-
-    def render(self, template):
-        """
-        Replace each $-substitutions in the given template string with
-        the corresponding terminal control string (if it's defined) or
-        '' (if it's not).
-        """
-        return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
-
-    def _render_sub(self, match):
-        s = match.group()
-        if s == '$$': return s
-        else: return getattr(self, s[2:-1])
-
-    def is_color(self, s):
-        try:
-            attr = getattr(self, s.upper())
-            return attr != None
-        except: return False
-
-class CliDisplay(object):
-    """
-    Display output for various syntax elements.
-    """
-    def __init__(self):
-        self.no_pretty = False
-    def set_no_pretty(self):
-        self.no_pretty = True
-    def reset_no_pretty(self):
-        self.no_pretty = False
-    def colorstring(self, clrnum, s):
-        if self.no_pretty:
-            return s
-        else:
-            return termctrl.render("${%s}%s${NORMAL}" % \
-                (user_prefs.colorscheme[clrnum].upper(), s))
-    def keyword(self, kw):
-        s = kw
-        if "uppercase" in user_prefs.output:
-            s = s.upper()
-        if "color" in user_prefs.output:
-            s = self.colorstring(0, s)
-        return s
-    def otherword(self, n, s):
-        if "color" in user_prefs.output:
-            return self.colorstring(n, s)
-        else:
-            return s
-    def id(self, s):
-        return self.otherword(1, s)
-    def attr_name(self, s):
-        return self.otherword(2, s)
-    def attr_value(self, s):
-        return self.otherword(3, s)
-    def rscref(self, s):
-        return self.otherword(4, s)
-    def score(self, s):
-        return self.otherword(5, s)
-
-global_aliases = {
-    "quit": ("bye","exit"),
-    "end": ("cd","up"),
-}
-def setup_aliases(obj):
-    for cmd in obj.cmd_aliases.keys():
-        for alias in obj.cmd_aliases[cmd]:
-            if obj.help_table:
-                obj.help_table[alias] = obj.help_table[cmd]
-            obj.cmd_table[alias] = obj.cmd_table[cmd]
-
-#
-# Resource Agents interface (meta-data, parameters, etc)
-#
-ocf_root = os.getenv("OCF_ROOT")
-if not ocf_root:
-    ocf_root = "@OCF_ROOT_DIR@"
-    if not ocf_root:
-        ocf_root = "/usr/lib/ocf"
-    os.putenv("OCF_ROOT",ocf_root)
-class RaLrmd(object):
-    '''
-    Getting information from the resource agents.
-    '''
-    lrmadmin_prog = "lrmadmin"
-    def __init__(self):
-        self.good = self.is_lrmd_accessible()
-    def lrmadmin(self, opts, xml = False):
-        '''
-        Get information directly from lrmd using lrmadmin.
-        '''
-        l = stdout2list("%s %s" % (self.lrmadmin_prog,opts))
-        if l and not xml:
-            l = l[1:] # skip the first line
-        return l
-    def is_lrmd_accessible(self):
-        if not (is_program(self.lrmadmin_prog) and is_process("lrmd")):
-            return False
-        return subprocess.call(\
-            add_sudo(">/dev/null 2>&1 %s -C" % self.lrmadmin_prog), \
-            shell=True) == 0
-    def meta(self, ra_class,ra_type,ra_provider):
-        return self.lrmadmin("-M %s %s %s"%(ra_class,ra_type,ra_provider),True)
-    def providers(self, ra_type,ra_class = "ocf"):
-        'List of providers for a class:type.'
-        return self.lrmadmin("-P %s %s" % (ra_class,ra_type),True)
-    def classes(self):
-        'List of providers for a class:type.'
-        return self.lrmadmin("-C")
-    def types(self, ra_class = "ocf", ra_provider = ""):
-        'List of types for a class.'
-        return self.lrmadmin("-T %s" % ra_class)
-
-def os_types_list(path):
-    l = []
-    for f in glob.glob(path):
-        if os.access(f,os.X_OK) and os.path.isfile(f):
-            a = f.split("/")
-            l.append(a[-1])
-    return l
-class RaOS(object):
-    '''
-    Getting information from the resource agents (direct).
-    '''
-    def __init__(self):
-        self.good = True
-    def meta(self, ra_class,ra_type,ra_provider):
-        l = []
-        if ra_class == "ocf":
-            l = stdout2list("%s/resource.d/%s/%s meta-data" % \
-                (ocf_root,ra_provider,ra_type))
-        elif ra_class == "stonith":
-            l = stdout2list("stonith -m -t %s" % ra_type)
-        return l
-    def providers(self, ra_type,ra_class = "ocf"):
-        'List of providers for a class:type.'
-        l = []
-        if ra_class == "ocf":
-            for s in glob.glob("%s/resource.d/*/%s" % (ocf_root,ra_type)):
-                a = s.split("/")
-                if len(a) == 7:
-                    l.append(a[5])
-        return l
-    def classes(self):
-        'List of classes.'
-        return "heartbeat lsb ocf stonith".split()
-    def types(self, ra_class = "ocf", ra_provider = ""):
-        'List of types for a class.'
-        l = []
-        prov = ra_provider and ra_provider or "*"
-        if ra_class == "ocf":
-            l = os_types_list("%s/resource.d/%s/*" % (ocf_root,prov))
-        elif ra_class == "lsb":
-            l = os_types_list("/etc/init.d/*")
-        elif ra_class == "stonith":
-            l = stdout2list("stonith -L")
-        l = list(set(l))
-        l.sort()
-        return l
-
-def ra_classes():
-    '''
-    List of RA classes.
-    '''
-    if wcache.is_cached("ra_classes"):
-        return wcache.retrieve("ra_classes")
-    l = ra_if.classes()
-    l.sort()
-    return wcache.store("ra_classes",l)
-def ra_providers(ra_type,ra_class = "ocf"):
-    'List of providers for a class:type.'
-    id = "ra_providers-%s-%s" % (ra_class,ra_type)
-    if wcache.is_cached(id):
-        return wcache.retrieve(id)
-    l = ra_if.providers(ra_type,ra_class)
-    l.sort()
-    return wcache.store(id,l)
-def ra_providers_all(ra_class = "ocf"):
-    '''
-    List of providers for a class.
-    '''
-    id = "ra_providers_all-%s" % ra_class
-    if wcache.is_cached(id):
-        return wcache.retrieve(id)
-    dir = ocf_root + "/resource.d"
-    l = []
-    for s in os.listdir(dir):
-        if os.path.isdir("%s/%s" % (dir,s)):
-            l.append(s)
-    l.sort()
-    return wcache.store(id,l)
-def ra_types(ra_class = "ocf", ra_provider = ""):
-    '''
-    List of RA type for a class.
-    '''
-    if not ra_class:
-        ra_class = "ocf"
-    id = "ra_types-%s-%s" % (ra_class,ra_provider)
-    if wcache.is_cached(id):
-        return wcache.retrieve(id)
-    if ra_provider:
-        list = []
-        for ra in ra_if.types(ra_class):
-            if ra_provider in ra_providers(ra,ra_class):
-                list.append(ra)
-    else:
-        list = ra_if.types(ra_class)
-    list.sort()
-    return wcache.store(id,list)
-
-def prog_meta(s):
-    '''
-    Do external program metadata.
-    '''
-    prog = "@CRM_DAEMON_DIR@/%s" % s
-    l = []
-    if is_program(prog):
-        l = stdout2list("%s metadata" % prog)
-    return l
-def get_nodes_text(n,tag):
-    try:
-        node = n.getElementsByTagName(tag)[0]
-        for c in node.childNodes:
-            if c.nodeType == c.TEXT_NODE:
-                return c.data.strip()
-    except: return ''
-
-def mk_monitor_name(role,depth):
-    depth = depth == "0" and "" or ("_%s" % depth)
-    return role and role != "Started" and \
-        "monitor_%s%s" % (role,depth) or \
-        "monitor%s" % depth
-def monitor_name_node(node):
-    depth = node.getAttribute("depth") or '0'
-    role = node.getAttribute("role")
-    return mk_monitor_name(role,depth)
-def monitor_name_pl(pl):
-    depth = find_value(pl, "depth") or '0'
-    role = find_value(pl, "role")
-    return mk_monitor_name(role,depth)
-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("\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:
-        return -1
-    return (int(r.group(1))*mult)/div
-def crm_time_cmp(a, b):
-    return crm_msec(a) - crm_msec(b)
-
-class RAInfo(object):
-    '''
-    A resource agent and whatever's useful about it.
-    '''
-    ra_tab = "    "  # four horses
-    required_ops = ("start", "stop")
-    skip_ops = ("meta-data", "validate-all")
-    skip_op_attr = ("name", "depth", "role")
-    def __init__(self,ra_class,ra_type,ra_provider = "heartbeat"):
-        self.ra_class = ra_class
-        self.ra_type = ra_type
-        self.ra_provider = ra_provider
-        if not self.ra_provider:
-            self.ra_provider = "heartbeat"
-        self.ra_node = None
-    def ra_string(self):
-        return self.ra_class == "ocf" and \
-            "%s:%s:%s" % (self.ra_class, self.ra_provider, self.ra_type) or \
-            "%s:%s" % (self.ra_class, self.ra_type)
-    def error(self, s):
-        common_err("%s: %s" % (self.ra_string(), s))
-    def warn(self, s):
-        common_warn("%s: %s" % (self.ra_string(), s))
-    def add_extra_stonith_params(self):
-        if not stonithd_metadata.mk_ra_node():
-            return
-        try:
-            params_node = self.doc.getElementsByTagName("parameters")[0]
-        except:
-            params_node = self.doc.createElement("parameters")
-            self.ra_node.appendChild(params_node)
-        for n in stonithd_metadata.ra_node.getElementsByTagName("parameter"):
-            params_node.appendChild(self.doc.importNode(n,1))
-    def mk_ra_node(self):
-        '''
-        Return the resource_agent node.
-        '''
-        if self.ra_node:
-            return self.ra_node
-        meta = self.meta()
-        try:
-            self.doc = xml.dom.minidom.parseString('\n'.join(meta))
-        except:
-            #common_err("could not parse meta-data for (%s,%s,%s)" \
-            #    % (self.ra_class,self.ra_type,self.ra_provider))
-            self.ra_node = None
-            return None
-        try:
-            self.ra_node = self.doc.getElementsByTagName("resource-agent")[0]
-        except:
-            self.error("meta-data contains no resource-agent element")
-            self.ra_node = None
-            return None
-        if self.ra_class == "stonith":
-            self.add_extra_stonith_params()
-        return self.ra_node
-    def param_type_default(self,n):
-        try:
-            content = n.getElementsByTagName("content")[0]
-            type = content.getAttribute("type")
-            default = content.getAttribute("default")
-            return type,default
-        except:
-            return None,None
-    def params(self):
-        '''
-        Construct a dict of dicts: parameters are keys and
-        dictionary of attributes/values are values. Cached too.
-        '''
-        id = "ra_params-%s" % self.ra_string()
-        if wcache.is_cached(id):
-            return wcache.retrieve(id)
-        if not self.mk_ra_node():
-            return None
-        d = {}
-        for pset in self.ra_node.getElementsByTagName("parameters"):
-            for c in pset.getElementsByTagName("parameter"):
-                name = c.getAttribute("name")
-                if not name:
-                    continue
-                required = c.getAttribute("required")
-                unique = c.getAttribute("unique")
-                type,default = self.param_type_default(c)
-                d[name] = {
-                    "required": required,
-                    "unique": unique,
-                    "type": type,
-                    "default": default,
-                }
-        return wcache.store(id,d)
-    def actions(self):
-        '''
-        Construct a dict of dicts: actions are keys and
-        dictionary of attributes/values are values. Cached too.
-        '''
-        id = "ra_actions-%s" % self.ra_string()
-        if wcache.is_cached(id):
-            return wcache.retrieve(id)
-        if not self.mk_ra_node():
-            return None
-        d = {}
-        for pset in self.ra_node.getElementsByTagName("actions"):
-            for c in pset.getElementsByTagName("action"):
-                name = c.getAttribute("name")
-                if not name or name in self.skip_ops:
-                    continue
-                if name == "monitor":
-                    name = monitor_name_node(c)
-                d[name] = {}
-                for a in c.attributes.keys():
-                    if a in self.skip_op_attr:
-                        continue
-                    v = c.getAttribute(a)
-                    if v:
-                        d[name][a] = v
-        # add monitor ops without role, if they don't already
-        # exist
-        d2 = {}
-        for op in d.keys():
-            if re.match("monitor_[^0-9]", op):
-                norole_op = re.sub(r'monitor_[^0-9_]+_(.*)', r'monitor_\1', op)
-                if not norole_op in d:
-                    d2[norole_op] = d[op]
-        d.update(d2)
-        return wcache.store(id,d)
-    def reqd_params_list(self):
-        '''
-        List of required parameters.
-        '''
-        d = self.params()
-        if not d: return []
-        return [x for x in d if d[x]["required"] == '1']
-    def param_default(self,pname):
-        '''
-        Parameter's default.
-        '''
-        d = self.params()
-        if not d: return None
-        return d[pname]["default"]
-    def sanity_check_params(self, id, pl):
-        '''
-        pl is a list of (attribute,value) pairs.
-        - are all required parameters defined
-        - do all parameters exist
-        '''
-        rc = 0
-        d = {}
-        for p,v in pl:
-            d[p] = v
-        for p in self.reqd_params_list():
-            if p not in d:
-                common_err("%s: required parameter %s not defined" % (id,p))
-                rc |= user_prefs.get_check_rc()
-        for p in d:
-            if p not in self.params():
-                common_err("%s: parameter %s does not exist" % (id,p))
-                rc |= user_prefs.get_check_rc()
-        return rc
-    def sanity_check_ops(self, id, ops):
-        '''
-        ops is a dict, operation names are keys and values are
-        lists of (attribute,value) pairs.
-        - do all operations exist
-        - are timeouts sensible
-        '''
-        rc = 0
-        n_ops = {}
-        for op in ops:
-            n_op = op == "monitor" and monitor_name_pl(ops[op]) or op
-            n_ops[n_op] = {}
-            for p,v in ops[op]:
-                if p in self.skip_op_attr:
-                    continue
-                n_ops[n_op][p] = v
-        default_timeout = get_default("default-action-timeout")
-        for req_op in self.required_ops:
-            if req_op not in n_ops:
-                n_ops[req_op] = {}
-        for op in n_ops:
-            if op not in self.actions():
-                common_warn("%s: action %s not advertised in meta-data, it may not be supported by the RA" % (id,op))
-                rc |= 1
-                continue
-            try:
-                adv_timeout = self.actions()[op]["timeout"]
-            except:
-                continue
-            for a in n_ops[op]:
-                v = n_ops[op][a]
-                if a == "timeout":
-                    if crm_msec(v) < 0:
-                        continue
-                    if crm_time_cmp(adv_timeout,v) > 0:
-                        common_warn("%s: timeout %s for %s is smaller than the advised %s" % \
-                            (id,v,op,adv_timeout))
-                        rc |= 1
-        return rc
-    def meta(self):
-        '''
-        RA meta-data as raw xml.
-        '''
-        id = "ra_meta-%s" % self.ra_string()
-        if wcache.is_cached(id):
-            return wcache.retrieve(id)
-        if self.ra_class in ("pengine","stonithd"):
-            l = prog_meta(self.ra_class)
-        else:
-            l = ra_if.meta(self.ra_class,self.ra_type,self.ra_provider)
-        return wcache.store(id, l)
-    def meta_pretty(self):
-        '''
-        Print the RA meta-data in a human readable form.
-        '''
-        if not self.mk_ra_node():
-            return ''
-        l = []
-        title = self.meta_title()
-        l.append(title)
-        longdesc = get_nodes_text(self.ra_node,"longdesc")
-        if longdesc:
-            l.append(longdesc)
-        if self.ra_class != "heartbeat":
-            params = self.meta_parameters()
-            if params:
-                l.append(params.rstrip())
-        actions = self.meta_actions()
-        if actions:
-            l.append(actions)
-        return '\n\n'.join(l)
-    def get_shortdesc(self,n):
-        name = n.getAttribute("name")
-        shortdesc = get_nodes_text(n,"shortdesc")
-        longdesc = get_nodes_text(n,"longdesc")
-        if shortdesc and shortdesc not in (name,longdesc,self.ra_type):
-            return shortdesc
-        return ''
-    def meta_title(self):
-        s = self.ra_string()
-        shortdesc = self.get_shortdesc(self.ra_node)
-        if shortdesc:
-            s = "%s (%s)" % (shortdesc,s)
-        return s
-    def meta_param_head(self,n):
-        name = n.getAttribute("name")
-        if not name:
-            return None
-        s = name
-        if n.getAttribute("required") == "1":
-            s = s + "*"
-        type,default = self.param_type_default(n)
-        if type and default:
-            s = "%s (%s, [%s])" % (s,type,default)
-        elif type:
-            s = "%s (%s)" % (s,type)
-        shortdesc = self.get_shortdesc(n)
-        s = "%s: %s" % (s,shortdesc)
-        return s
-    def format_parameter(self,n):
-        l = []
-        head = self.meta_param_head(n)
-        if not head:
-            self.error("no name attribute for parameter")
-            return ""
-        l.append(head)
-        longdesc = get_nodes_text(n,"longdesc")
-        if longdesc:
-            longdesc = self.ra_tab + longdesc.replace("\n","\n"+self.ra_tab) + '\n'
-            l.append(longdesc)
-        return '\n'.join(l)
-    def meta_parameter(self,param):
-        if not self.mk_ra_node():
-            return ''
-        l = []
-        for pset in self.ra_node.getElementsByTagName("parameters"):
-            for c in pset.getElementsByTagName("parameter"):
-                if c.getAttribute("name") == param:
-                    return self.format_parameter(c)
-    def meta_parameters(self):
-        if not self.mk_ra_node():
-            return ''
-        l = []
-        for pset in self.ra_node.getElementsByTagName("parameters"):
-            for c in pset.getElementsByTagName("parameter"):
-                s = self.format_parameter(c)
-                if s:
-                    l.append(s)
-        if l:
-            return "Parameters (* denotes required, [] the default):\n\n" + '\n'.join(l)
-    def meta_action_head(self,n):
-        name = n.getAttribute("name")
-        if not name:
-            return ''
-        if name in self.skip_ops:
-            return ''
-        if name == "monitor":
-            name = monitor_name_node(n)
-        s = "%-13s" % name
-        for a in n.attributes.keys():
-            if a in self.skip_op_attr:
-                continue
-            v = n.getAttribute(a)
-            if v:
-                s = "%s %s=%s" % (s,a,v)
-        return s
-    def meta_actions(self):
-        l = []
-        for aset in self.ra_node.getElementsByTagName("actions"):
-            for c in aset.getElementsByTagName("action"):
-                s = self.meta_action_head(c)
-                if s:
-                    l.append(self.ra_tab + s)
-        if l:
-            return "Operations' defaults (advisory minimum):\n\n" + '\n'.join(l)
-
-def cmd_end(cmd,dir = ".."):
-    "Go up one level."
-    levels.droplevel()
-def cmd_exit(cmd):
-    "Exit the crm program"
-    cmd_end(cmd)
-    if interactive:
-        print "bye"
-    try:
-        readline.write_history_file(hist_file)
-    except:
-        pass
-    for f in tmpfiles:
-        os.unlink(f)
-    sys.exit()
-
-#
-# help or make users feel less lonely
-#
-def add_shorthelp(topic,shorthelp,topic_help):
-    '''
-    Join topics ("%s,%s") if they share the same short
-    description.
-    '''
-    for i in range(len(topic_help)):
-        if topic_help[i][1] == shorthelp:
-            topic_help[i][0] = "%s,%s" % (topic_help[i][0], topic)
-            return
-    topic_help.append([topic, shorthelp])
-def dump_short_help(help_tab):
-    topic_help = []
-    for topic in help_tab:
-        if topic == '.':
-            continue
-        # with odict, for whatever reason, python parses differently:
-        # help_tab["..."] = ("...","...") and
-        # help_tab["..."] = ("...","""
-        # ...""")
-        # a parser bug?
-        if type(help_tab[topic][0]) == type(()):
-            shorthelp = help_tab[topic][0][0]
-        else:
-            shorthelp = help_tab[topic][0]
-        add_shorthelp(topic,shorthelp,topic_help)
-    for t,d in topic_help:
-        print "\t%-16s %s" % (t,d)
-def overview(help_tab):
-    print ""
-    print help_tab['.'][1]
-    print ""
-    print "Available commands:"
-    print ""
-    dump_short_help(help_tab)
-    print ""
-def topic_help(help_tab,topic):
-    if topic not in help_tab:
-        print "There is no help for topic %s" % topic
-        return
-    if type(help_tab[topic][0]) == type(()):
-        shorthelp = help_tab[topic][0][0]
-        longhelp = help_tab[topic][0][1]
-    else:
-        shorthelp = help_tab[topic][0]
-        longhelp = help_tab[topic][1]
-    if longhelp:
-        page_string(longhelp)
-    else:
-        print shorthelp
-def cmd_help(help_tab,topic = ''):
-    "help!"
-    # help_tab is an odict (ordered dictionary):
-    # help_tab[topic] = (short_help,long_help)
-    # topic '.' is a special entry for the top level
-    if not help_tab:
-        common_info("sorry, help not available")
-        return
-    if not topic:
-        overview(help_tab)
-    else:
-        topic_help(help_tab,topic)
-
-class UserInterface(object):
-    '''
-    Stuff common to all user interface classes.
-    '''
-    def __init__(self):
-        self.cmd_table = odict()
-        self.cmd_table["help"] = (self.help,(0,1),0)
-        self.cmd_table["quit"] = (self.exit,(0,0),0)
-        self.cmd_table["end"] = (self.end,(0,1),0)
-        self.cmd_aliases = global_aliases.copy()
-    def end_game(self, no_questions_asked = False):
-        pass
-    def help(self,cmd,topic = ''):
-        "usage: help [<topic>]"
-        cmd_help(self.help_table,topic)
-    def end(self,cmd,dir = ".."):
-        "usage: end"
-        self.end_game()
-        cmd_end(cmd,dir)
-    def exit(self,cmd):
-        "usage: exit"
-        self.end_game()
-        cmd_exit(cmd)
-
-def add_sudo(cmd):
-    if user_prefs.crm_user:
-        return "sudo -E -u %s %s"%(user_prefs.crm_user,cmd)
-    return cmd
-def pipe_string(cmd,s):
-    rc = -1 # command failed
-    cmd = add_sudo(cmd)
-    p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
-    try:
-        p.communicate(s)
-        p.wait()
-        rc = p.returncode
-    except IOError, msg:
-        common_err(msg)
-    return rc
-def cibdump2doc(section = None):
-    doc = None
-    if section:
-        cmd = "%s -o %s" % (cib_dump,section)
-    else:
-        cmd = cib_dump
-    cmd = add_sudo(cmd)
-    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
-    try:
-        doc = xmlparse(p.stdout)
-        p.wait()
-    except IOError, msg:
-        common_err(msg)
-        return None
-    return doc
-def file2doc(s):
-    try: f = open(s,'r')
-    except IOError, msg:
-        common_err(msg)
-        return None
-    doc = xmlparse(f)
-    f.close()
-    return doc
-def shadow2doc(name):
-    return file2doc(shadowfile(name))
-
-def str2tmp(s):
-    '''
-    Write the given string to a temporary file. Return the name
-    of the file.
-    '''
-    fd,tmp = mkstemp()
-    try: f = os.fdopen(fd,"w")
-    except IOError, msg:
-        common_err(msg)
-        return
-    f.write(s)
-    f.close()
-    return tmp
-
-def is_filename_sane(name):
-    if re.search("['`/#*?$\[\]]",name):
-        common_err("%s: bad name"%name)
-        return False
-    return True
-def is_name_sane(name):
-    if re.search("[']",name):
-        common_err("%s: bad name"%name)
-        return False
-    return True
-def is_value_sane(name):
-    if re.search("[']",name):
-        common_err("%s: bad name"%name)
-        return False
-    return True
-
-def ext_cmd(cmd):
-    if regression_tests:
-        print ".EXT", cmd
-    return subprocess.call(add_sudo(cmd), shell=True)
-
-def get_stdout(cmd, stderr_on = True):
-    '''
-    Run a cmd, return stdin output.
-    stderr_on controls whether to show output which comes on stderr.
-    '''
-    if stderr_on:
-        stderr = None
-    else:
-        stderr = subprocess.PIPE
-    proc = subprocess.Popen(cmd, shell = True, \
-        stdout = subprocess.PIPE, stderr = stderr)
-    outp = proc.communicate()[0]
-    proc.wait()
-    outp = outp.strip()
-    return outp
-def stdout2list(cmd, stderr_on = True):
-    '''
-    Run a cmd, fetch output, return it as a list of lines.
-    stderr_on controls whether to show output which comes on stderr.
-    '''
-    s = get_stdout(add_sudo(cmd), stderr_on)
-    return s.split('\n')
-
-def find_program(envvar,*args):
-    if envvar and os.getenv(envvar):
-        return os.getenv(envvar)
-    for prog in args:
-        if is_program(prog):
-            return prog
-
-def is_id_valid(id):
-    """
-    Verify that the id follows the definition:
-    http://www.w3.org/TR/1999/REC-xml-names-19990114/#ns-qualnames
-    """
-    if not id:
-        return False
-    id_re = "^[A-Za-z_][\w._-]*$"
-    return re.match(id_re,id)
-def check_filename(fname):
-    """
-    Verify that the string is a filename.
-    """
-    fname_re = "^[^/]+$"
-    return re.match(fname_re,id)
-
-class UserPrefs(object):
-    '''
-    Keep user preferences here.
-    '''
-    dflt_colorscheme = "yellow,normal,cyan,red,green,magenta".split(',')
-    skill_levels = {"operator":0, "administrator":1, "expert":2}
-    output_types = ("plain", "color", "uppercase")
-    check_frequencies = ("always", "on-verify", "never")
-    check_modes = ("strict", "relaxed")
-    def __init__(self):
-        self.skill_level = 2 #TODO: set back to 0?
-        self.editor = find_program("EDITOR","vim","vi","emacs","nano")
-        self.pager = find_program("PAGER","less","more","pg")
-        self.dotty = find_program("","dotty")
-        if not self.editor:
-            missing_prog_warn("editor")
-        if not self.pager:
-            missing_prog_warn("pager")
-        self.crm_user = ""
-        self.xmlindent = "  "  # two spaces
-        # keywords,ids,attribute names,values
-        self.colorscheme = self.dflt_colorscheme
-        # plain or color
-        self.output = ['color',]
-        # the semantic checks preferences
-        self.check_frequency = "always"
-        self.check_mode = "strict"
-        self.debug = False
-        self.force = False
-    def check_skill_level(self,n):
-        return self.skill_level >= n
-    def set_skill_level(self,skill_level):
-        if skill_level in self.skill_levels:
-            self.skill_level = self.skill_levels[skill_level]
-        else:
-            common_err("no %s skill level"%skill_level)
-            return False
-    def get_skill_level(self):
-        for s in self.skill_levels:
-            if self.skill_level == self.skill_levels[s]:
-                return s
-    def set_editor(self,prog):
-        if is_program(prog):
-            self.editor = prog
-        else:
-            common_err("program %s does not exist"% prog)
-            return False
-    def set_pager(self,prog):
-        if is_program(prog):
-            self.pager = prog
-        else:
-            common_err("program %s does not exist"% prog)
-            return False
-    def set_crm_user(self,user = ''):
-        self.crm_user = user
-    def set_output(self,otypes):
-        l = otypes.split(',')
-        for otype in l:
-            if not otype in self.output_types:
-                common_err("no %s output type" % otype)
-                return False
-        self.output = l
-    def set_colors(self,scheme):
-        colors = scheme.split(',')
-        if len(colors) != 6:
-            common_err("bad color scheme: %s"%scheme)
-            colors = UserPrefs.dflt_colorscheme
-        rc = True
-        for c in colors:
-            if not termctrl.is_color(c):
-                common_err("%s is not a recognized color" % c)
-                rc = False
-        if rc:
-            self.colorscheme = colors
-        else:
-            self.output.remove("color")
-        return rc
-    def is_check_always(self):
-        '''
-        Even though the frequency may be set to always, it doesn't
-        make sense to do that with non-interactive sessions.
-        '''
-        return interactive and self.check_frequency == "always"
-    def get_check_rc(self):
-        '''
-        If the check mode is set to strict, then on errors we
-        return 2 which is the code for error. Otherwise, we
-        pretend that errors are warnings.
-        '''
-        return self.check_mode == "strict" and 2 or 1
-    def set_check_freq(self,frequency):
-        if frequency not in self.check_frequencies:
-            common_err("no %s check frequency"%frequency)
-            return False
-        self.check_frequency = frequency
-    def set_check_mode(self,mode):
-        if mode not in self.check_modes:
-            common_err("no %s check mode"%mode)
-            return False
-        self.check_mode = mode
-    def set_debug(self):
-        self.debug = True
-    def get_debug(self):
-        return self.debug
-    def set_force(self):
-        self.force = True
-    def get_force(self):
-        return self.force
-    def write_rc(self,f):
-        print >>f, '%s "%s"' % ("editor",self.editor)
-        print >>f, '%s "%s"' % ("pager",self.pager)
-        print >>f, '%s "%s"' % ("user",self.crm_user)
-        print >>f, '%s "%s"' % ("skill-level",self.get_skill_level())
-        print >>f, '%s "%s"' % ("output", ','.join(self.output))
-        print >>f, '%s "%s"' % ("colorscheme", ','.join(self.colorscheme))
-        print >>f, '%s "%s"' % ("check-frequency",self.check_frequency)
-        print >>f, '%s "%s"' % ("check-mode",self.check_mode)
-    def save_options(self):
-        try: f = open(rc_file,"w")
-        except IOError,msg:
-            common_err("open: %s"%msg)
-            return
-        print >>f, 'options'
-        self.write_rc(f)
-        print >>f, 'end'
-        f.close()
-
-class CliOptions(UserInterface):
-    '''
-    Manage user preferences
-    '''
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("options")
-        self.cmd_table["skill-level"] = (self.set_skill_level,(1,1),0,(skills_list,))
-        self.cmd_table["editor"] = (self.set_editor,(1,1),0)
-        self.cmd_table["pager"] = (self.set_pager,(1,1),0)
-        self.cmd_table["user"] = (self.set_crm_user,(0,1),0)
-        self.cmd_table["output"] = (self.set_output,(1,1),0)
-        self.cmd_table["colorscheme"] = (self.set_colors,(1,1),0)
-        self.cmd_table["check-frequency"] = (self.set_check_frequency,(1,1),0)
-        self.cmd_table["check-mode"] = (self.set_check_mode,(1,1),0)
-        self.cmd_table["save"] = (self.save_options,(0,0),0)
-        self.cmd_table["show"] = (self.show_options,(0,0),0)
-        setup_aliases(self)
-    def set_skill_level(self,cmd,skill_level):
-        """usage: skill-level <level>
-        level: operator | administrator | expert"""
-        return user_prefs.set_skill_level(skill_level)
-    def set_editor(self,cmd,prog):
-        "usage: editor <program>"
-        return user_prefs.set_editor(prog)
-    def set_pager(self,cmd,prog):
-        "usage: pager <program>"
-        return user_prefs.set_pager(prog)
-    def set_crm_user(self,cmd,user = ''):
-        "usage: user [<crm_user>]"
-        return user_prefs.set_crm_user(user)
-    def set_output(self,cmd,otypes):
-        "usage: output <type>"
-        return user_prefs.set_output(otypes)
-    def set_colors(self,cmd,scheme):
-        "usage: colorscheme <colors>"
-        return user_prefs.set_colors(scheme)
-    def set_check_frequency(self,cmd,freq):
-        "usage: check-frequence <freq>"
-        return user_prefs.set_check_freq(freq)
-    def set_check_mode(self,cmd,mode):
-        "usage: check-mode <mode>"
-        return user_prefs.set_check_mode(mode)
-    def show_options(self,cmd):
-        "usage: show"
-        return user_prefs.write_rc(sys.stdout)
-    def save_options(self,cmd):
-        "usage: save"
-        return user_prefs.save_options()
-    def end_game(self, no_questions_asked = False):
-        if no_questions_asked and not interactive:
-            self.save_options("save")
-
-cib_dump = "cibadmin -Ql"
-cib_piped = "cibadmin -p"
-cib_upgrade = "cibadmin --upgrade --force"
-cib_verify = "crm_verify -V -p"
-
-class WCache(object):
-    "Cache stuff. A naive implementation."
-    def __init__(self):
-        self.lists = {}
-        self.stamp = time.time()
-        self.max_cache_age = 600 # seconds
-    def is_cached(self,name):
-        if time.time() - self.stamp > self.max_cache_age:
-            self.stamp = time.time()
-            self.clear()
-        return name in self.lists
-    def store(self,name,lst):
-        self.lists[name] = lst
-        return lst
-    def retrieve(self,name):
-        if self.is_cached(name):
-            return self.lists[name]
-        else:
-            return None
-    def clear(self):
-        self.lists = {}
-
-def listshadows():
-    return stdout2list("ls @CRM_CONFIG_DIR@ | fgrep shadow. | sed 's/^shadow\.//'")
-def shadowfile(name):
-    return "@CRM_CONFIG_DIR@/shadow.%s" % name
-
-class CibShadow(UserInterface):
-    '''
-    CIB shadow management class
-    '''
-    envvar = "CIB_shadow"
-    extcmd = ">/dev/null </dev/null crm_shadow"
-    extcmd_stdout = "</dev/null crm_shadow"
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("cib")
-        self.cmd_table["new"] = (self.new,(1,3),1)
-        self.cmd_table["delete"] = (self.delete,(1,1),1,(shadows_list,))
-        self.cmd_table["reset"] = (self.reset,(1,1),1,(shadows_list,))
-        self.cmd_table["commit"] = (self.commit,(1,1),1,(shadows_list,))
-        self.cmd_table["use"] = (self.use,(0,2),1,(shadows_live_list,))
-        self.cmd_table["diff"] = (self.diff,(0,0),1)
-        self.cmd_table["list"] = (self.list,(0,0),1)
-        self.cmd_table["cibstatus"] = StatusMgmt
-        self.chkcmd()
-        setup_aliases(self)
-    def chkcmd(self):
-        try:
-            ext_cmd("%s 2>&1" % self.extcmd)
-        except os.error:
-            no_prog_err(self.extcmd)
-            return False
-        return True
-    def new(self,cmd,name,*args):
-        "usage: new <shadow_cib> [withstatus] [force]"
-        if not is_filename_sane(name):
-            return False
-        new_cmd = "%s -c '%s'" % (self.extcmd,name)
-        for par in args:
-            if not par in ("force","--force","withstatus"):
-                syntax_err((cmd,name,par), context = 'new')
-                return False
-        if user_prefs.get_force() or "force" in args or "--force" in args:
-            new_cmd = "%s --force" % new_cmd
-        if ext_cmd(new_cmd) == 0:
-            common_info("%s shadow CIB created"%name)
-            self.use("use",name)
-            if "withstatus" in args:
-                cib_status.load("shadow:%s" % name)
-    def delete(self,cmd,name):
-        "usage: delete <shadow_cib>"
-        if not is_filename_sane(name):
-            return False
-        if cib_in_use == name:
-            common_err("%s shadow CIB is in use"%name)
-            return False
-        if ext_cmd("%s -D '%s' --force" % (self.extcmd,name)) == 0:
-            common_info("%s shadow CIB deleted"%name)
-        else:
-            common_err("failed to delete %s shadow CIB"%name)
-            return False
-    def reset(self,cmd,name):
-        "usage: reset <shadow_cib>"
-        if not is_filename_sane(name):
-            return False
-        if ext_cmd("%s -r '%s'" % (self.extcmd,name)) == 0:
-            common_info("copied live CIB to %s"%name)
-        else:
-            common_err("failed to copy live CIB to %s"%name)
-            return False
-    def commit(self,cmd,name):
-        "usage: commit <shadow_cib>"
-        if not is_filename_sane(name):
-            return False
-        if ext_cmd("%s -C '%s' --force" % (self.extcmd,name)) == 0:
-            common_info("commited '%s' shadow CIB to the cluster"%name)
-            wcache.clear()
-        else:
-            common_err("failed to commit the %s shadow CIB"%name)
-            return False
-    def diff(self,cmd):
-        "usage: diff"
-        s = get_stdout(add_sudo("%s -d" % self.extcmd_stdout))
-        page_string(s)
-    def list(self,cmd):
-        "usage: list"
-        if regression_tests:
-            for t in listshadows():
-                print t
-        else:
-            multicolumn(listshadows())
-    def _use(self,name,withstatus):
-        # Choose a shadow cib for further changes. If the name
-        # provided is empty, then choose the live (cluster) cib.
-        # Don't allow ' in shadow names
-        global cib_in_use
-        if not name or name == "live":
-            os.unsetenv(self.envvar)
-            cib_in_use = ""
-            if withstatus:
-                cib_status.load("live")
-        else:
-            os.putenv(self.envvar,name)
-            cib_in_use = name
-            if withstatus:
-                cib_status.load("shadow:%s" % name)
-    def use(self,cmd,name = '', withstatus = ''):
-        "usage: use [<shadow_cib>] [withstatus]"
-        # check the name argument
-        if name and not is_filename_sane(name):
-            return False
-        if name and name != "live":
-            if not os.access(shadowfile(name),os.F_OK):
-                common_err("%s: no such shadow CIB"%name)
-                return False
-        if withstatus and withstatus != "withstatus":
-            syntax_err((cmd,withstatus), context = 'use')
-            return False
-        # If invoked from configure
-        # take special precautions
-        try:
-            prev_level = levels.previous().myname()
-        except:
-            prev_level = ''
-        if prev_level != "cibconfig":
-            self._use(name,withstatus)
-            return True
-        if not cib_factory.has_cib_changed():
-            self._use(name,withstatus)
-            # new CIB: refresh the CIB factory
-            cib_factory.refresh()
-            return True
-        saved_cib = cib_in_use
-        self._use(name,'') # don't load the status yet
-        if not cib_factory.is_current_cib_equal(silent = True):
-            # user made changes and now wants to switch to a
-            # different and unequal CIB; we refuse to cooperate
-            common_err("the requested CIB is different from the current one")
-            if user_prefs.get_force():
-                common_info("CIB overwrite forced")
-            elif not ask("All changes will be dropped. Do you want to proceed?"):
-                self._use(saved_cib,'') # revert to the previous CIB
-                return False
-        self._use(name,withstatus) # now load the status too
-        return True
-
-def get_var(l,key):
-    for s in l:
-        a = s.split()
-        if len(a) == 2 and a[0] == key:
-            return a[1]
-    return ''
-def chk_var(l,key):
-    for s in l:
-        a = s.split()
-        if len(a) == 2 and a[0] == key and a[1]:
-            return True
-    return False
-def chk_key(l,key):
-    for s in l:
-        a = s.split()
-        if len(a) >= 1 and a[0] == key:
-            return True
-    return False
-def validate_template(l):
-    'Test for required stuff in a template.'
-    if not chk_var(l,'%name'):
-        common_err("invalid template: missing '%name'")
-        return False
-    if not chk_key(l,'%generate'):
-        common_err("invalid template: missing '%generate'")
-        return False
-    g = l.index('%generate')
-    if not (chk_key(l[0:g],'%required') or chk_key(l[0:g],'%optional')):
-        common_err("invalid template: missing '%required' or '%optional'")
-        return False
-    return True
-def fix_tmpl_refs(l,id,pfx):
-    for i in range(len(l)):
-        l[i] = l[i].replace(id,pfx)
-def fix_tmpl_refs_re(l,regex,repl):
-    for i in range(len(l)):
-        l[i] = re.sub(regex,repl,l[i])
-class LoadTemplate(object):
-    '''
-    Load a template and its dependencies, generate a
-    configuration file which should be relatively easy and
-    straightforward to parse.
-    '''
-    edit_instructions = '''# Edit instructions:
-#
-# Add content only at the end of lines starting with '%%'.
-# Only add content, don't remove or replace anything.
-# The parameters following '%required' are not optional,
-# unlike those following '%optional'.
-# You may also add comments for future reference.'''
-    no_more_edit = '''# Don't edit anything below this line.'''
-    def __init__(self,name):
-        self.name = name
-        self.all_pre_gen = []
-        self.all_post_gen = []
-        self.all_pfx = []
-    def new_pfx(self,name):
-        i = 1
-        pfx = name
-        while pfx in self.all_pfx:
-            pfx = "%s_%d" % (name,i)
-            i += 1
-        self.all_pfx.append(pfx)
-        return pfx
-    def generate(self):
-        return '\n'.join([ \
-            "# Configuration: %s" % self.name, \
-            '', \
-            self.edit_instructions, \
-            '', \
-            '\n'.join(self.all_pre_gen), \
-            self.no_more_edit, \
-            '', \
-            '%generate', \
-            '\n'.join(self.all_post_gen)])
-    def write_config(self,name):
-        try:
-            f = open("%s/%s" % (Template.conf_dir, name),"w")
-        except IOError,msg:
-            common_err("open: %s"%msg)
-            return False
-        print >>f, self.generate()
-        f.close()
-        return True
-    def load_template(self,tmpl):
-        try:
-            f = open("%s/%s" % (Template.tmpl_dir, tmpl))
-        except IOError,msg:
-            common_err("open: %s"%msg)
-            return ''
-        l = (''.join(f)).split('\n')
-        if not validate_template(l):
-            return ''
-        common_info("pulling in template %s" % tmpl)
-        g = l.index('%generate')
-        pre_gen = l[0:g]
-        post_gen = l[g+1:]
-        name = get_var(pre_gen,'%name')
-        for s in l[0:g]:
-            if s.startswith('%depends_on'):
-                a = s.split()
-                if len(a) != 2:
-                    common_warn("%s: wrong usage" % s)
-                    continue
-                tmpl_id = a[1]
-                tmpl_pfx = self.load_template(a[1])
-                if tmpl_pfx:
-                    fix_tmpl_refs(post_gen,'%'+tmpl_id,'%'+tmpl_pfx)
-        pfx = self.new_pfx(name)
-        fix_tmpl_refs(post_gen, '%_:', '%'+pfx+':')
-        # replace remaining %_, it may be useful at times
-        fix_tmpl_refs(post_gen, '%_', pfx)
-        v_idx = pre_gen.index('%required') or pre_gen.index('%optional')
-        pre_gen.insert(v_idx,'%pfx ' + pfx)
-        self.all_pre_gen += pre_gen
-        self.all_post_gen += post_gen
-        return pfx
-    def post_process(self, params):
-        pfx_re = '(%s)' % '|'.join(self.all_pfx)
-        for n in params:
-            fix_tmpl_refs(self.all_pre_gen, '%% '+n, "%% "+n+"  "+params[n])
-        fix_tmpl_refs_re(self.all_post_gen, \
-            '%'+pfx_re+'([^:]|$)', r'\1\2')
-        # process %if ... [%else] ... %fi
-        rmidx_l = []
-        if_seq = False
-        for i in range(len(self.all_post_gen)):
-            s = self.all_post_gen[i]
-            if if_seq:
-                a = s.split()
-                if len(a) >= 1 and a[0] == '%fi':
-                    if_seq = False
-                    rmidx_l.append(i)
-                elif len(a) >= 1 and a[0] == '%else':
-                    outcome = not outcome
-                    rmidx_l.append(i)
-                else:
-                    if not outcome:
-                        rmidx_l.append(i)
-                continue
-            if not s:
-                continue
-            a = s.split()
-            if len(a) == 2 and a[0] == '%if':
-                outcome = not a[1].startswith('%') # not replaced -> false
-                if_seq = True
-                rmidx_l.append(i)
-        rmidx_l.reverse()
-        for i in rmidx_l:
-            del self.all_post_gen[i]
-
-def listtemplates():
-    l = []
-    for f in os.listdir(Template.tmpl_dir):
-        if os.path.isfile("%s/%s" % (Template.tmpl_dir,f)):
-            l.append(f)
-    return l
-def listconfigs():
-    l = []
-    for f in os.listdir(Template.conf_dir):
-        if os.path.isfile("%s/%s" % (Template.conf_dir,f)):
-            l.append(f)
-    return l
-def check_transition(inp,state,possible_l):
-    if not state in possible_l:
-        common_err("input (%s) in wrong state %s" % (inp,state))
-        return False
-    return True
-class Template(UserInterface):
-    '''
-    Configuration templates.
-    '''
-    conf_dir = "%s/%s" % (os.getenv("HOME"),".crmconf")
-    tmpl_dir = "@datadir@/@PACKAGE@/templates"
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("template")
-        self.cmd_table["new"] = (self.new,(2,),1,(null_list,templates_list,loop))
-        self.cmd_table["load"] = (self.load,(0,1),1,(config_list,))
-        self.cmd_table["edit"] = (self.edit,(0,1),1,(config_list,))
-        self.cmd_table["delete"] = (self.delete,(1,2),1,(config_list,))
-        self.cmd_table["show"] = (self.show,(0,1),0,(config_list,))
-        self.cmd_table["apply"] = (self.apply,(0,2),1,(config_list_method,config_list))
-        self.cmd_table["list"] = (self.list,(0,1),0)
-        setup_aliases(self)
-        self.init_dir()
-        self.curr_conf = ''
-    def init_dir(self):
-        '''Create the conf directory, link to templates'''
-        if not os.path.isdir(self.conf_dir):
-            try:
-                os.makedirs(self.conf_dir)
-            except os.error,msg:
-                common_err("makedirs: %s"%msg)
-                return
-    def get_depends(self,tmpl):
-        '''return a list of required templates'''
-        # Not used. May need it later.
-        try:
-            tf = open("%s/%s" % (self.tmpl_dir, tmpl),"r")
-        except IOError,msg:
-            common_err("open: %s"%msg)
-            return
-        l = []
-        for s in tf:
-            a = s.split()
-            if len(a) >= 2 and a[0] == '%depends_on':
-                l += a[1:]
-        tf.close()
-        return l
-    def replace_params(self,s,user_data):
-        change = False
-        for i in range(len(s)):
-            word = s[i]
-            for p in user_data:
-                # is parameter in the word?
-                pos = word.find('%' + p) 
-                if pos < 0:
-                    continue
-                endpos = pos + len('%' + p)
-                # and it isn't part of another word?
-                if re.match("[A-Za-z0-9]", word[endpos:endpos+1]):
-                    continue
-                # if the value contains a space or
-                # it is a value of an attribute
-                # put quotes around it
-                if user_data[p].find(' ') >= 0 or word[pos-1:pos] == '=':
-                    v = '"' + user_data[p] + '"'
-                else:
-                    v = user_data[p]
-                word = word.replace('%' + p, v)
-                change = True # we did replace something
-            if change:
-                s[i] = word
-        if 'opt' in s:
-            if not change:
-                s = []
-            else:
-                s.remove('opt')
-        return s
-    def generate(self,l,user_data):
-        '''replace parameters (user_data) and generate output
-        '''
-        l2 = []
-        for piece in l:
-            piece2 = []
-            for s in piece:
-                s = self.replace_params(s,user_data)
-                if s:
-                    piece2.append(' '.join(s))
-            if piece2:
-                l2.append(' \\\n\t'.join(piece2))
-        return '\n'.join(l2)
-    def process(self,config = ''):
-        '''Create a cli configuration from the current config'''
-        try:
-            f = open("%s/%s" % (self.conf_dir, config or self.curr_conf),'r')
-        except IOError,msg:
-            common_err("open: %s"%msg)
-            return ''
-        l = []
-        piece = []
-        user_data = {}
-        # states
-        START = 0; PFX = 1; DATA = 2; GENERATE = 3
-        state = START
-        global lineno
-        save_lineno = lineno
-        lineno = 0
-        rc = True
-        for inp in f:
-            lineno += 1
-            if inp.startswith('#'):
-                continue
-            if type(inp) == type(u''):
-                inp = inp.encode('ascii')
-            inp = inp.strip()
-            try:
-                s = shlex.split(inp)
-            except ValueError, msg:
-                common_err(msg)
-                continue
-            while '\n' in s:
-                s.remove('\n')
-            if not s:
-                if state == GENERATE and piece:
-                    l.append(piece)
-                    piece = []
-            elif s[0] in ("%name","%depends_on","%suggests"):
-                continue
-            elif s[0] == "%pfx":
-                if check_transition(inp,state,(START,DATA)) and len(s) == 2:
-                    pfx = s[1]
-                    state = PFX
-            elif s[0] == "%required":
-                if check_transition(inp,state,(PFX,)):
-                    state = DATA
-                    data_reqd = True
-            elif s[0] == "%optional":
-                if check_transition(inp,state,(PFX,DATA)):
-                    state = DATA
-                    data_reqd = False
-            elif s[0] == "%%":
-                if state != DATA:
-                    common_warn("user data in wrong state %s" % state)
-                if len(s) < 2:
-                    common_warn("parameter name missing")
-                elif len(s) == 2:
-                    if data_reqd:
-                        common_err("required parameter %s not set" % s[1])
-                        rc = False
-                elif len(s) == 3:
-                    user_data["%s:%s" % (pfx,s[1])] = s[2]
-                else:
-                    common_err("%s: syntax error" % inp)
-            elif s[0] == "%generate":
-                if check_transition(inp,state,(DATA,)):
-                    state = GENERATE
-                    piece = []
-            elif state == GENERATE:
-                if s:
-                    piece.append(s)
-            else:
-                common_err("<%s> unexpected" % inp)
-        if piece:
-            l.append(piece)
-        lineno = save_lineno
-        f.close()
-        if not rc:
-            return ''
-        return self.generate(l,user_data)
-    def new(self,cmd,name,*args):
-        "usage: new <config> <template> [<template> ...] [params name=value ...]"
-        if not is_filename_sane(name):
-            return False
-        if os.path.isfile("%s/%s" % (self.conf_dir, name)):
-            common_err("config %s exists; delete it first" % name)
-            return False
-        lt = LoadTemplate(name)
-        rc = True
-        mode = 0
-        params = {}
-        for s in args:
-            if mode == 0 and s == "params":
-                params["id"] = name
-                mode = 1
-            elif mode == 1:
-                a = s.split('=')
-                if len(a) != 2:
-                    syntax_err(args, context = 'new')
-                    rc = False
-                else:
-                    params[a[0]] = a[1]
-            elif not lt.load_template(s):
-                rc = False
-        if rc:
-            lt.post_process(params)
-        if not rc or not lt.write_config(name):
-            return False
-        self.curr_conf = name
-    def config_exists(self,name):
-        if not is_filename_sane(name):
-            return False
-        if not os.path.isfile("%s/%s" % (self.conf_dir, name)):
-            common_err("%s: no such config" % name)
-            return False
-        return True
-    def delete(self,cmd,name,force = ''):
-        "usage: delete <config> [force]"
-        if force:
-            if force != "force" and force != "--force":
-                syntax_err((cmd,force), context = 'delete')
-                return False
-        if not self.config_exists(name):
-            return False
-        if name == self.curr_conf:
-            if not force and not user_prefs.get_force() and \
-                    not ask("Do you really want to remove config %s which is in use?" % self.curr_conf):
-                return False
-            else:
-                self.curr_conf = ''
-        os.remove("%s/%s" % (self.conf_dir, name))
-    def load(self,cmd,name = ''):
-        "usage: load [<config>]"
-        if not name:
-            self.curr_conf = ''
-            return True
-        if not self.config_exists(name):
-            return False
-        self.curr_conf = name
-    def edit(self,cmd,name = ''):
-        "usage: edit [<config>]"
-        if not name and not self.curr_conf:
-            common_err("please load a config first")
-            return False
-        if name:
-            if not self.config_exists(name):
-                return False
-            edit_file("%s/%s" % (self.conf_dir, name))
-        else:
-            edit_file("%s/%s" % (self.conf_dir, self.curr_conf))
-    def show(self,cmd,name = ''):
-        "usage: show [<config>]"
-        if not name and not self.curr_conf:
-            common_err("please load a config first")
-            return False
-        if name:
-            if not self.config_exists(name):
-                return False
-            print self.process(name)
-        else:
-            print self.process()
-    def apply(self,cmd,*args):
-        "usage: apply [<method>] [<config>]"
-        method = "replace"
-        name = ''
-        if len(args) > 0:
-            i = 0
-            if args[0] in ("replace","update"):
-                method = args[0]
-                i += 1
-            if len(args) > i:
-                name = args[i]
-        if not name and not self.curr_conf:
-            common_err("please load a config first")
-            return False
-        if name:
-            if not self.config_exists(name):
-                return False
-            s = self.process(name)
-        else:
-            s = self.process()
-        if not s:
-            return False
-        tmp = str2tmp(s)
-        if not tmp:
-            return False
-        set_obj = mkset_obj("NOOBJ")
-        rc = set_obj.import_file(method,tmp)
-        try: os.unlink(tmp)
-        except: pass
-        return rc
-    def list(self,cmd,templates = ''):
-        "usage: list [templates]"
-        if templates == "templates":
-            multicolumn(listtemplates())
-        else:
-            multicolumn(listconfigs())
-
-def manage_attr(cmd,attr_ext_commands,*args):
-    if len(args) < 3:
-        bad_usage(cmd,' '.join(args))
-        return False
-    attr_cmd = None
-    try:
-        attr_cmd = attr_ext_commands[args[1]]
-    except KeyError:
-        bad_usage(cmd,' '.join(args))
-        return False
-    if not attr_cmd:
-        bad_usage(cmd,' '.join(args))
-        return False
-    if args[1] == 'set':
-        if len(args) == 4:
-            if not is_name_sane(args[0]) \
-                    or not is_name_sane(args[2]) \
-                    or not is_value_sane(args[3]):
-                return False
-            return ext_cmd(attr_cmd%(args[0],args[2],args[3])) == 0
-        else:
-            bad_usage(cmd,' '.join(args))
-            return False
-    elif args[1] in ('delete','show'):
-        if len(args) == 3:
-            if not is_name_sane(args[0]) \
-                    or not is_name_sane(args[2]):
-                return False
-            return ext_cmd(attr_cmd%(args[0],args[2])) == 0
-        else:
-            bad_usage(cmd,' '.join(args))
-            return False
-    else:
-        bad_usage(cmd,' '.join(args))
-        return False
-
-def resources_xml():
-    if wcache.is_cached("rsc_xml"):
-        return wcache.retrieve("rsc_xml")
-    doc = cibdump2doc("resources")
-    if not doc:
-        return []
-    return wcache.store("rsc_xml",doc)
-def rsc2node(id):
-    if wcache.is_cached("rsc_%s_node" % id):
-        return wcache.retrieve("rsc_%s_node" % id)
-    doc = resources_xml()
-    if not doc:
-        return []
-    nodes = get_interesting_nodes(doc,[])
-    for n in nodes:
-        if is_resource(n) and n.getAttribute("id") == id:
-            return wcache.store("rsc_%s_node" % id, n)
-def get_meta_param(id,param):
-    return get_stdout(RscMgmt.rsc_meta['show'] % (id,param), stderr_on = False)
-def is_live_cib():
-    '''We working with the live cluster?'''
-    return not cib_in_use and not os.getenv("CIB_file")
-def is_rsc_running(id):
-    if not is_live_cib():
-        return False
-    rsc_node = rsc2node(id)
-    if not rsc_node:
-        return False
-    if not is_resource(rsc_node):
-        return False
-    test_id = rsc_clone(id) or id
-    outp = get_stdout(RscMgmt.rsc_status % test_id, stderr_on = False)
-    return outp.find("running") > 0 and outp.find("NOT") == -1
-def is_rsc_clone(rsc_id):
-    rsc_node = rsc2node(rsc_id)
-    return is_clone(rsc_node)
-def is_rsc_ms(rsc_id):
-    rsc_node = rsc2node(rsc_id)
-    return is_ms(rsc_node)
-def rsc_clone(rsc_id):
-    '''Get a clone of a resource.'''
-    rsc_node = rsc2node(rsc_id)
-    if not rsc_node or not rsc_node.parentNode:
-        return None
-    pnode = rsc_node.parentNode
-    if is_group(pnode):
-        pnode = pnode.parentNode
-    if is_clonems(pnode):
-        return pnode.getAttribute("id")
-def is_process(s):
-    proc = subprocess.Popen("ps -e -o pid,command | grep -qs '%s'" % s, \
-        shell=True, stdout=subprocess.PIPE)
-    proc.wait()
-    return proc.returncode == 0
-def cluster_stack():
-    if is_process("heartbeat:.[m]aster"):
-        return "heartbeat"
-    elif is_process("[a]isexec"):
-        return "openais"
-    return ""
-def get_cloned_rsc(rsc_id):
-    rsc_node = rsc2node(rsc_id)
-    if not rsc_node:
-        return ""
-    for c in rsc_node.childNodes:
-        if is_child_rsc(c):
-            return c.getAttribute("id")
-    return ""
-def get_max_clone(id):
-    v = get_meta_param(id,"clone-max")
-    try:
-        cnt = int(v)
-    except:
-        cnt = len(listnodes())
-    return cnt
-def cleanup_resource(rsc,node):
-    if not is_name_sane(rsc) or not is_name_sane(node):
-        return False
-    if is_rsc_clone(rsc) or is_rsc_ms(rsc):
-        base = get_cloned_rsc(rsc)
-        if not base:
-            return False
-        clone_max = get_max_clone(rsc)
-        rc = True
-        for n in range(clone_max):
-            if ext_cmd(RscMgmt.rsc_cleanup % ("%s:%d" % (base,n), node)) != 0:
-                rc = False
-    else:
-        rc = ext_cmd(RscMgmt.rsc_cleanup%(rsc,node)) != 0
-    return rc
-
-class RscMgmt(UserInterface):
-    '''
-    Resources management class
-    '''
-    rsc_status_all = "crm_resource -L"
-    rsc_status = "crm_resource -W -r '%s'"
-    rsc_showxml = "crm_resource -q -r '%s'"
-    rsc_setrole = "crm_resource --meta -r '%s' -p target-role -v '%s'"
-    rsc_manage = "crm_resource --meta -r '%s' -p is-managed -v '%s'"
-    rsc_migrate = "crm_resource -M -r '%s'"
-    rsc_migrateto = "crm_resource -M -r '%s' -H '%s'"
-    rsc_unmigrate = "crm_resource -U -r '%s'"
-    rsc_cleanup = "crm_resource -C -r '%s' -H '%s'"
-    rsc_param =  {
-        'set': "crm_resource -r '%s' -p '%s' -v '%s'",
-        'delete': "crm_resource -r '%s' -d '%s'",
-        'show': "crm_resource -r '%s' -g '%s'",
-    }
-    rsc_meta =  {
-        'set': "crm_resource --meta -r '%s' -p '%s' -v '%s'",
-        'delete': "crm_resource --meta -r '%s' -d '%s'",
-        'show': "crm_resource --meta -r '%s' -g '%s'",
-    }
-    rsc_failcount = {
-        'set': "crm_failcount -r '%s' -N '%s' -v '%s'",
-        'delete': "crm_failcount -r '%s' -N '%s' -D",
-        'show': "crm_failcount -r '%s' -N '%s' -G",
-    }
-    rsc_refresh = "crm_resource -R"
-    rsc_refresh_node = "crm_resource -R -H '%s'"
-    rsc_reprobe = "crm_resource -P"
-    rsc_reprobe_node = "crm_resource -P -H '%s'"
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("resource")
-        self.cmd_table["status"] = (self.status,(0,1),0,(rsc_list,))
-        self.cmd_table["start"] = (self.start,(1,1),0,(rsc_list,))
-        self.cmd_table["stop"] = (self.stop,(1,1),0,(rsc_list,))
-        self.cmd_table["restart"] = (self.restart,(1,1),0,(rsc_list,))
-        self.cmd_table["promote"] = (self.promote,(1,1),0,(rsc_list,))
-        self.cmd_table["demote"] = (self.demote,(1,1),0,(rsc_list,))
-        self.cmd_table["manage"] = (self.manage,(1,1),0,(rsc_list,))
-        self.cmd_table["unmanage"] = (self.unmanage,(1,1),0,(rsc_list,))
-        self.cmd_table["migrate"] = (self.migrate,(1,2),0,(rsc_list,nodes_list))
-        self.cmd_table["unmigrate"] = (self.unmigrate,(1,1),0,(rsc_list,))
-        self.cmd_table["param"] = (self.param,(3,4),1,(rsc_list,attr_cmds))
-        self.cmd_table["meta"] = (self.meta,(3,4),1,(rsc_list,attr_cmds))
-        self.cmd_table["failcount"] = (self.failcount,(3,4),0,(rsc_list,attr_cmds,nodes_list))
-        self.cmd_table["cleanup"] = (self.cleanup,(1,2),1,(rsc_list,nodes_list))
-        self.cmd_table["refresh"] = (self.refresh,(0,1),0,(nodes_list,))
-        self.cmd_table["reprobe"] = (self.reprobe,(0,1),0,(nodes_list,))
-        self.cmd_aliases.update({
-            "status": ("show","list",),
-            "migrate": ("move",),
-            "unmigrate": ("unmove",),
-        })
-        setup_aliases(self)
-    def status(self,cmd,rsc = None):
-        "usage: status [<rsc>]"
-        if rsc:
-            if not is_name_sane(rsc):
-                return False
-            return ext_cmd(self.rsc_status % rsc) == 0
-        else:
-            return ext_cmd(self.rsc_status_all) == 0
-    def start(self,cmd,rsc):
-        "usage: start <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        return ext_cmd(self.rsc_setrole%(rsc,"Started")) == 0
-    def restart(self,cmd,rsc):
-        "usage: restart <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        if not self.stop("stop",rsc):
-            return False
-        return self.start("start",rsc)
-    def stop(self,cmd,rsc):
-        "usage: stop <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        return ext_cmd(self.rsc_setrole%(rsc,"Stopped")) == 0
-    def promote(self,cmd,rsc):
-        "usage: promote <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        if not is_rsc_ms(rsc):
-            common_err("%s is not a master-slave resource" % rsc)
-            return False
-        return ext_cmd(self.rsc_setrole%(rsc,"Master")) == 0
-    def demote(self,cmd,rsc):
-        "usage: demote <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        if not is_rsc_ms(rsc):
-            common_err("%s is not a master-slave resource" % rsc)
-            return False
-        return ext_cmd(self.rsc_setrole%(rsc,"Slave")) == 0
-    def manage(self,cmd,rsc):
-        "usage: manage <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        return ext_cmd(self.rsc_manage%(rsc,"true")) == 0
-    def unmanage(self,cmd,rsc):
-        "usage: unmanage <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        return ext_cmd(self.rsc_manage%(rsc,"false")) == 0
-    def migrate(self,cmd,*args):
-        """usage: migrate <rsc> [<node>]"""
-        if not is_name_sane(args[0]):
-            return False
-        if len(args) == 1:
-            return ext_cmd(self.rsc_migrate%args[0]) == 0
-        else:
-            if not is_name_sane(args[1]):
-                return False
-            return ext_cmd(self.rsc_migrateto%(args[0],args[1])) == 0
-    def unmigrate(self,cmd,rsc):
-        "usage: unmigrate <rsc>"
-        if not is_name_sane(rsc):
-            return False
-        return ext_cmd(self.rsc_unmigrate%rsc) == 0
-    def cleanup(self,cmd,*args):
-        "usage: cleanup <rsc> [<node>]"
-        # Cleanup a resource on a node. Omit node to cleanup on
-        # all live nodes.
-        if len(args) == 2: # remove
-            return cleanup_resource(args[0],args[1])
-        else:
-            rv = True
-            for n in listnodes():
-                if not cleanup_resource(args[0],n):
-                    rv = False
-            return rv
-    def failcount(self,cmd,*args):
-        """usage:
-        failcount <rsc> set <node> <value>
-        failcount <rsc> delete <node>
-        failcount <rsc> show <node>"""
-        d = lambda: manage_attr(cmd,self.rsc_failcount,*args)
-        return d()
-    def param(self,cmd,*args):
-        """usage:
-        param <rsc> set <param> <value>
-        param <rsc> delete <param>
-        param <rsc> show <param>"""
-        d = lambda: manage_attr(cmd,self.rsc_param,*args)
-        return d()
-    def meta(self,cmd,*args):
-        """usage:
-        meta <rsc> set <attr> <value>
-        meta <rsc> delete <attr>
-        meta <rsc> show <attr>"""
-        d = lambda: manage_attr(cmd,self.rsc_meta,*args)
-        return d()
-    def refresh(self,cmd,*args):
-        'usage: refresh [<node>]'
-        if len(args) == 1:
-            if not is_name_sane(args[0]):
-                return False
-            return ext_cmd(self.rsc_refresh_node%args[0]) == 0
-        else:
-            return ext_cmd(self.rsc_refresh) == 0
-    def reprobe(self,cmd,*args):
-        'usage: reprobe [<node>]'
-        if len(args) == 1:
-            if not is_name_sane(args[0]):
-                return False
-            return ext_cmd(self.rsc_reprobe_node%args[0]) == 0
-        else:
-            return ext_cmd(self.rsc_reprobe) == 0
-
-def print_node(uname,id,node_type,other,inst_attr,offline):
-    """
-    Try to pretty print a node from the cib. Sth like:
-    uname(id): node_type
-        attr1: v1
-        attr2: v2
-    """
-    s_offline = offline and "(offline)" or ""
-    if uname == id:
-        print "%s: %s%s" % (uname,node_type,s_offline)
-    else:
-        print "%s(%s): %s%s" % (uname,id,node_type,s_offline)
-    for a in other:
-        print "\t%s: %s" % (a,other[a])
-    for a,v in inst_attr:
-        print "\t%s: %s" % (a,v)
-
-class NodeMgmt(UserInterface):
-    '''
-    Nodes management class
-    '''
-    node_standby = "crm_standby -N '%s' -v '%s'"
-    node_delete = "cibadmin -D -o nodes -X '<node uname=\"%s\"/>'"
-    node_delete_status = "cibadmin -D -o status -X '<node_state uname=\"%s\"/>'"
-    hb_delnode = "@libdir@/heartbeat/hb_delnode '%s'"
-    crm_node = "crm_node"
-    node_fence = "crm_attribute -t status -U '%s' -n terminate -v true"
-    dc = "crmadmin -D"
-    node_attr = {
-        'set': "crm_attribute -t nodes -U '%s' -n '%s' -v '%s'",
-        'delete': "crm_attribute -D -t nodes -U '%s' -n '%s'",
-        'show': "crm_attribute -G -t nodes -U '%s' -n '%s'",
-    }
-    node_status = {
-        'set': "crm_attribute -t status -U '%s' -n '%s' -v '%s'",
-        'delete': "crm_attribute -D -t status -U '%s' -n '%s'",
-        'show': "crm_attribute -G -t status -U '%s' -n '%s'",
-    }
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("node")
-        self.cmd_table["status"] = (self.status,(0,1),0,(nodes_list,))
-        self.cmd_table["show"] = (self.show,(0,1),0,(nodes_list,))
-        self.cmd_table["standby"] = (self.standby,(0,1),0,(nodes_list,))
-        self.cmd_table["online"] = (self.online,(0,1),0,(nodes_list,))
-        self.cmd_table["fence"] = (self.fence,(1,1),0,(nodes_list,))
-        self.cmd_table["delete"] = (self.delete,(1,1),0,(nodes_list,))
-        self.cmd_table["attribute"] = (self.attribute,(3,4),0,(nodes_list,attr_cmds))
-        self.cmd_table["status-attr"] = (self.status_attr,(3,4),0,(nodes_list,attr_cmds))
-        self.cmd_aliases.update({
-            "show": ("list",),
-        })
-        setup_aliases(self)
-    def status(self,cmd,node = None):
-        'usage: status [<node>]'
-        return ext_cmd("%s -o nodes"%cib_dump) == 0
-    def show(self,cmd,node = None):
-        'usage: show [<node>]'
-        doc = cibdump2doc()
-        if not doc:
-            return False
-        nodes_node = get_conf_elem(doc, "nodes")
-        status = get_conf_elem(doc, "status")
-        if not nodes_node:
-            return False
-        for c in nodes_node.childNodes:
-            if not is_element(c) or c.tagName != "node":
-                continue
-            if node and c.getAttribute("uname") != node:
-                continue
-            type = uname = id = ""
-            inst_attr = []
-            other = {}
-            for attr in c.attributes.keys():
-                v = c.getAttribute(attr)
-                if attr == "type":
-                    type = v
-                elif attr == "uname":
-                    uname = v
-                elif attr == "id":
-                    id = v
-                else:
-                    other[attr] = v
-            for c2 in c.childNodes:
-                if not is_element(c2):
-                    continue
-                if c2.tagName == "instance_attributes":
-                    inst_attr += nvpairs2list(c2)
-            offline = False
-            for c2 in status.getElementsByTagName("node_state"):
-                if uname != c2.getAttribute("uname"):
-                    continue
-                offline = c2.getAttribute("crmd") == "offline"
-            print_node(uname,id,type,other,inst_attr,offline)
-    def standby(self,cmd,node = None):
-        'usage: standby [<node>]'
-        if not node:
-            node = this_node
-        if not is_name_sane(node):
-            return False
-        return ext_cmd(self.node_standby%(node,"on")) == 0
-    def online(self,cmd,node = None):
-        'usage: online [<node>]'
-        if not node:
-            node = this_node
-        if not is_name_sane(node):
-            return False
-        return ext_cmd(self.node_standby%(node,"off")) == 0
-    def fence(self,cmd,node):
-        'usage: fence <node>'
-        if not node:
-            node = this_node
-        if not is_name_sane(node):
-            return False
-        return ext_cmd(self.node_fence%(node)) == 0
-    def delete(self,cmd,node):
-        'usage: delete <node>'
-        if not is_name_sane(node):
-            return False
-        rc = True
-        if cluster_stack() == "heartbeat":
-            rc = ext_cmd(self.hb_delnode%node) == 0
-        else:
-            node_states = {}
-            for s in stdout2list("%s -l" % self.crm_node):
-                a = s.split()
-                if len(a) != 3:
-                    common_warn("%s bad format: %s" % (self.crm_node,s))
-                    continue
-                # fmt: id uname status
-                # remove only those in state "lost"
-                if a[1] == node:
-                    node_states[a[2]] = 1
-                    if a[2] == "lost":
-                        if ext_cmd("%s --force -R %s" % (self.crm_node,a[0])) != 0:
-                            rc = False
-            if not "lost" in node_states:
-                common_err('node %s/state "lost" not found in the id list' % node)
-                if "member" in node_states:
-                    common_info("node %s appears to be still active" % node)
-                common_info("check output of %s -l" % self.crm_node)
-                rc = False
-        if rc:
-            if ext_cmd(self.node_delete%node) != 0 or \
-                    ext_cmd(self.node_delete_status%node) != 0:
-                rc = False
-        return rc
-    def attribute(self,cmd,*args):
-        """usage:
-        attribute <node> set <rsc> <value>
-        attribute <node> delete <rsc>
-        attribute <node> show <rsc>"""
-        d = lambda: manage_attr(cmd,self.node_attr,*args)
-        return d()
-    def status_attr(self,cmd,*args):
-        """usage:
-        status-attr <node> set <rsc> <value>
-        status-attr <node> delete <rsc>
-        status-attr <node> show <rsc>"""
-        d = lambda: manage_attr(cmd,self.node_status,*args)
-        return d()
-
-def edit_file(fname):
-    'Edit a file.'
-    if not fname:
-        return
-    if not user_prefs.editor:
-        return
-    return ext_cmd("%s %s" % (user_prefs.editor,fname))
-
-def page_string(s):
-    'Write string through a pager.'
-    if not s:
-        return
-    w,h = get_winsize()
-    if s.count('\n') <= h:
-        print s
-    elif not user_prefs.pager or not interactive:
-        print s
-    else:
-        opts = ""
-        if user_prefs.pager == "less":
-            opts = "-R"
-        pipe_string("%s %s" % (user_prefs.pager,opts), s)
-
-def lines2cli(s):
-    '''
-    Convert a string into a list of lines. Replace continuation
-    characters. Strip white space, left and right. Drop empty lines.
-    '''
-    cl = []
-    l = s.split('\n')
-    cum = []
-    for p in l:
-        p = p.strip()
-        if p.endswith('\\'):
-            p = p.rstrip('\\')
-            cum.append(p)
-        else:
-            cum.append(p)
-            cl.append(''.join(cum).strip())
-            cum = []
-    if cum: # in case s ends with backslash
-        cl.append(''.join(cum))
-    return [x for x in cl if x]
-
-def get_winsize():
-    try:
-        import curses
-        curses.setupterm()
-        w = curses.tigetnum('cols')
-        h = curses.tigetnum('lines')
-    except:
-        try:
-            w = os.environ['COLS']
-            h = os.environ['LINES']
-        except:
-            w = 80; h = 25
-    return w,h
-def multicolumn(l):
-    '''
-    A ls-like representation of a list of strings.
-    A naive approach.
-    '''
-    min_gap = 2
-    w,h = get_winsize()
-    max_len = 8
-    for s in l:
-        if len(s) > max_len:
-            max_len = len(s)
-    cols = w/(max_len + min_gap)  # approx.
-    col_len = w/cols
-    for i in range(len(l)/cols + 1):
-        s = ''
-        for j in range(i*cols,(i+1)*cols):
-            if not j < len(l):
-                break
-            if not s:
-                s = "%-*s" % (col_len,l[j])
-            elif (j+1)%cols == 0:
-                s = "%s%s" % (s,l[j])
-            else:
-                s = "%s%-*s" % (s,col_len,l[j])
-        if s:
-            print s
-
-class RA(UserInterface):
-    '''
-    CIB shadow management class
-    '''
-    provider_classes = ["ocf"]
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("ra")
-        self.cmd_table["classes"] = (self.classes,(0,0),0)
-        self.cmd_table["list"] = (self.list,(1,2),1)
-        self.cmd_table["providers"] = (self.providers,(1,1),1)
-        self.cmd_table["meta"] = (self.meta,(1,3),1)
-        self.cmd_aliases.update({
-            "meta": ("info",),
-        })
-        setup_aliases(self)
-    def classes(self,cmd):
-        "usage: classes"
-        for c in ra_classes():
-            if c in self.provider_classes:
-                print "%s / %s" % (c,' '.join(ra_providers_all(c)))
-            else:
-                print "%s" % c
-    def providers(self,cmd,ra_type):
-        "usage: providers <ra>"
-        print ' '.join(ra_providers(ra_type))
-    def list(self,cmd,c,p = None):
-        "usage: list <class> [<provider>]"
-        if not c in ra_classes():
-            common_err("class %s does not exist" % c)
-            return False
-        if p and not p in ra_providers_all(c):
-            common_err("there is no provider %s for class %s" % (p,c))
-            return False
-        if regression_tests:
-            for t in ra_types(c,p):
-                print t
-        else:
-            multicolumn(ra_types(c,p))
-    def meta(self,cmd,*args):
-        "usage: meta [<class>:[<provider>:]]<type>"
-        if len(args) > 1: # obsolete syntax
-            ra_type = args[0]
-            ra_class = args[1]
-            if len(args) < 3:
-                ra_provider = "heartbeat"
-            else:
-                ra_provider = args[2]
-        else:
-            ra_class,ra_provider,ra_type = disambiguate_ra_type(args[0])
-        ra = RAInfo(ra_class,ra_type,ra_provider)
-        if not ra.mk_ra_node():
-            return False
-        try:
-            page_string(ra.meta_pretty())
-        except:
-            return False
-
-class StatusMgmt(UserInterface):
-    '''
-    The CIB status section management user interface class
-    '''
-    lrm_exit_codes = {
-        "success": "0",
-        "unknown": "1",
-        "args": "2",
-        "unimplemented": "3",
-        "perm": "4",
-        "installed": "5",
-        "configured": "6",
-        "not_running": "7",
-        "master": "8",
-        "failed_master": "9",
-    }
-    lrm_status_codes = {
-        "pending": "-1",
-        "done": "0",
-        "cancelled": "1",
-        "timeout": "2",
-        "notsupported": "3",
-        "error": "4",
-    }
-    ra_operations = ("probe", "monitor", "start", "stop",
-       "promote", "demote", "notify", "migrate_to", "migrate_from")
-    node_states = ("online", "offline", "unclean")
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("cibstatus")
-        self.cmd_table["show"] = (self.show,(0,1),1)
-        self.cmd_table["save"] = (self.save,(0,1),2)
-        self.cmd_table["load"] = (self.load,(1,1),2)
-        self.cmd_table["origin"] = (self.origin,(0,0),1)
-        self.cmd_table["node"] = (self.edit_node,(2,2),2,(status_node_list,node_states_list))
-        self.cmd_table["op"] = (self.edit_op,(3,5),2,(ra_operations_list,status_rsc_list,lrm_exit_codes_list,lrm_status_codes_list,status_node_list))
-        setup_aliases(self)
-    def myname(self):
-        '''Just return some id.'''
-        return "cibstatus"
-    def load(self,cmd,org):
-        "usage: load {<file>|shadow:<cib>|live}"
-        return cib_status.load(org)
-    def save(self,cmd,dest = None):
-        "usage: save [<file>|shadow:<cib>]"
-        return cib_status.save(dest)
-    def origin(self,cmd):
-        "usage: origin"
-        state = cib_status.modified and " (modified)" or ""
-        print "%s%s" % (cib_status.origin,state)
-    def show(self,cmd,changed = ""):
-        "usage: show [changed]"
-        if changed:
-            if changed != "changed":
-                syntax_err((cmd,changed))
-                return False
-            else:
-                return cib_status.list_changes()
-        return cib_status.show()
-    def edit_node(self,cmd,node,state):
-        "usage: node <node> {online|offline|unclean}"
-        return cib_status.edit_node(node,state)
-    def edit_op(self,cmd,op,rsc,rc,op_status = None,node = ''):
-        "usage: op <operation> <resource> <exit_code> [<op_status>] [<node>]"
-        if rc in self.lrm_exit_codes:
-            num_rc = self.lrm_exit_codes[rc]
-        else:
-            num_rc = rc
-        if not num_rc.isdigit():
-            common_err("%s exit code invalid" % num_rc)
-            return False
-        num_op_status = op_status
-        if op_status:
-            if op_status in self.lrm_status_codes:
-                num_op_status = self.lrm_status_codes[op_status]
-            if not num_op_status.isdigit():
-                common_err("%s operation status invalid" % num_op_status)
-                return False
-        return cib_status.edit_op(op,rsc,num_rc,num_op_status,node)
-
-class CibConfig(UserInterface):
-    '''
-    The configuration class
-    '''
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.help_table = help_sys.load_level("configure")
-        self.cmd_table["erase"] = (self.erase,(0,1),1)
-        self.cmd_table["verify"] = (self.verify,(0,0),1)
-        self.cmd_table["refresh"] = (self.refresh,(0,0),1)
-        self.cmd_table["ptest"] = (self.ptest,(0,3),1)
-        self.cmd_table["commit"] = (self.commit,(0,1),1)
-        self.cmd_table["upgrade"] = (self.upgrade,(0,1),1)
-        self.cmd_table["show"] = (self.show,(0,),1,(id_xml_list,id_list,loop))
-        self.cmd_table["edit"] = (self.edit,(0,),1,(id_xml_list,id_list,loop))
-        self.cmd_table["delete"] = (self.delete,(1,),1,(id_list,loop))
-        self.cmd_table["rename"] = (self.rename,(2,2),1,(id_list,))
-        self.cmd_table["save"] = (self.save,(1,2),1)
-        self.cmd_table["load"] = (self.load,(2,3),1)
-        self.cmd_table["node"] = (self.conf_node,(1,),1)
-        self.cmd_table["primitive"] = (self.conf_primitive,(2,),1,(null_list, \
-                ra_classes_list, primitive_complete_complex, loop))
-        self.cmd_table["group"] = (self.conf_group,(2,),1,(null_list,f_prim_id_list,loop))
-        self.cmd_table["clone"] = (self.conf_clone,(2,),1,(null_list,f_children_id_list))
-        self.cmd_table["ms"] = (self.conf_ms,(2,),1,(null_list,f_children_id_list))
-        self.cmd_table["location"] = (self.conf_location,(2,),1,(null_list,rsc_id_list))
-        self.cmd_table["colocation"] = (self.conf_colocation,(2,),1,(null_list,null_list,rsc_id_list,loop))
-        self.cmd_table["order"] = (self.conf_order,(2,),1,(null_list,null_list,rsc_id_list,loop))
-        self.cmd_table["property"] = (self.conf_property,(1,),1,(property_complete,loop))
-        self.cmd_table["rsc_defaults"] = (self.conf_rsc_defaults,(1,),1)
-        self.cmd_table["op_defaults"] = (self.conf_op_defaults,(1,),1)
-        self.cmd_table["monitor"] = (self.conf_monitor,(2,2),1)
-        self.cmd_table["ra"] = RA
-        self.cmd_table["cib"] = CibShadow
-        self.cmd_table["cibstatus"] = StatusMgmt
-        self.cmd_table["template"] = Template
-        self.cmd_table["_test"] = (self.check_structure,(0,0),1)
-        self.cmd_table["_regtest"] = (self.regression_testing,(1,1),1)
-        self.cmd_table["_queues"] = (self.showqueues,(0,0),1)
-        self.cmd_table["_objects"] = (self.showobjects,(0,0),1)
-        self.cmd_aliases.update({
-            "colocation": ("collocation",),
-            "ms": ("master",),
-        })
-        setup_aliases(self)
-        cib_factory.initialize()
-    def myname(self):
-        '''Just return some id.'''
-        return "cibconfig"
-    def check_structure(self,cmd):
-        return cib_factory.check_structure()
-    def regression_testing(self,cmd,param):
-        return cib_factory.regression_testing(param)
-    def showqueues(self,cmd):
-        cib_factory.showqueues()
-    def showobjects(self,cmd):
-        cib_factory.showobjects()
-    def show(self,cmd,*args):
-        "usage: show [xml] [<id>...]"
-        if not cib_factory.is_cib_sane():
-            return False
-        err_buf.buffer() # keep error messages
-        set_obj = mkset_obj(*args)
-        err_buf.release() # show them, but get an ack from the user
-        return set_obj.show()
-    def edit(self,cmd,*args):
-        "usage: edit [xml] [<id>...]"
-        if not cib_factory.is_cib_sane():
-            return False
-        err_buf.buffer() # keep error messages
-        set_obj = mkset_obj(*args)
-        err_buf.release() # show them, but get an ack from the user
-        return set_obj.edit()
-    def verify(self,cmd):
-        "usage: verify"
-        if not cib_factory.is_cib_sane():
-            return False
-        set_obj = mkset_obj("xml")
-        rc1 = set_obj.verify()
-        if user_prefs.check_frequency != "never":
-            rc2 = set_obj.verify2()
-        else:
-            rc2 = 0
-        return rc1 and rc2 <= 1
-    def save(self,cmd,*args):
-        "usage: save [xml] <filename>"
-        if not cib_factory.is_cib_sane():
-            return False
-        if args[0] == "xml":
-            f = args[1]
-            set_obj = mkset_obj("xml")
-        else:
-            f = args[0]
-            set_obj = mkset_obj()
-        return set_obj.save_to_file(f)
-    def load(self,cmd,*args):
-        "usage: load [xml] {replace|update} {<url>|<path>}"
-        if not cib_factory.is_cib_sane():
-            return False
-        if args[0] == "xml":
-            if len(args) != 3:
-                syntax_err(args, context = 'load')
-                return False
-            url = args[2]
-            method = args[1]
-            set_obj = mkset_obj("xml","NOOBJ")
-        else:
-            if len(args) != 2:
-                syntax_err(args, context = 'load')
-                return False
-            url = args[1]
-            method = args[0]
-            set_obj = mkset_obj("NOOBJ")
-        return set_obj.import_file(method,url)
-    def delete(self,cmd,*args):
-        "usage: delete <id> [<id>...]"
-        if not cib_factory.is_cib_sane():
-            return False
-        return cib_factory.delete(*args)
-    def rename(self,cmd,old_id,new_id):
-        "usage: rename <old_id> <new_id>"
-        if not cib_factory.is_cib_sane():
-            return False
-        return cib_factory.rename(old_id,new_id)
-    def erase(self,cmd,nodes = None):
-        "usage: erase [nodes]"
-        if not cib_factory.is_cib_sane():
-            return False
-        if nodes:
-            if nodes == "nodes":
-                return cib_factory.erase_nodes()
-            else:
-                syntax_err((cmd,nodes), context = 'erase')
-        else:
-            return cib_factory.erase()
-    def refresh(self,cmd):
-        "usage: refresh"
-        if not cib_factory.is_cib_sane():
-            return False
-        if interactive and cib_factory.has_cib_changed():
-            if not ask("All changes will be dropped. Do you want to proceed?"):
-                return
-        cib_factory.refresh()
-    def ptest(self,cmd,*args):
-        "usage: ptest [nograph] [v...] [scores]"
-        if not cib_factory.is_cib_sane():
-            return False
-        verbosity = 'vv'  # default verbosity
-        nograph = False
-        scores = False
-        for p in args:
-            if p == "nograph":
-                nograph = True
-            elif p == "scores":
-                scores = True
-            elif re.match("^vv*$", p):
-                verbosity = p
-            else:
-                bad_usage(cmd,' '.join(args))
-                return False
-        set_obj = mkset_obj("xml")
-        return set_obj.ptest(nograph, scores, verbosity)
-    def commit(self,cmd,force = None):
-        "usage: commit [force]"
-        if force and force != "force":
-            syntax_err((cmd,force))
-            return False
-        if not cib_factory.is_cib_sane():
-            return False
-        if not cib_factory.has_cib_changed():
-            common_info("apparently there is nothing to commit")
-            common_info("try changing something first")
-            return
-        wcache.clear()
-        rc1 = cib_factory.is_current_cib_equal()
-        rc2 = self.verify("verify")
-        if rc1 and rc2:
-            return cib_factory.commit()
-        if force or user_prefs.get_force():
-            common_info("commit forced")
-            return cib_factory.commit()
-        if ask("Do you still want to commit?"):
-            return cib_factory.commit()
-        return False
-    def upgrade(self,cmd,force = None):
-        "usage: upgrade [force]"
-        if not cib_factory.is_cib_sane():
-            return False
-        if force and force != "force":
-            syntax_err((cmd,force))
-            return False
-        if user_prefs.get_force() or force:
-            return cib_factory.upgrade_cib_06to10(True)
-        else:
-            return cib_factory.upgrade_cib_06to10()
-    def __conf_object(self,cmd,*args):
-        "The configure object command."
-        if not cib_factory.is_cib_sane():
-            return False
-        f = lambda: cib_factory.create_object(cmd,*args)
-        return f()
-    def conf_node(self,cmd,*args):
-        """usage: node <uname>[:<type>]
-           [attributes <param>=<value> [<param>=<value>...]]"""
-        return self.__conf_object(cmd,*args)
-    def conf_primitive(self,cmd,*args):
-        """usage: primitive <rsc> [<class>:[<provider>:]]<type>
-        [params <param>=<value> [<param>=<value>...]]
-        [meta <attribute>=<value> [<attribute>=<value>...]]
-        [operations id_spec
-            [op op_type [<attribute>=<value>...] ...]]"""
-        return self.__conf_object(cmd,*args)
-    def conf_group(self,cmd,*args):
-        """usage: group <name> <rsc> [<rsc>...]
-        [params <param>=<value> [<param>=<value>...]]
-        [meta <attribute>=<value> [<attribute>=<value>...]]"""
-        return self.__conf_object(cmd,*args)
-    def conf_clone(self,cmd,*args):
-        """usage: clone <name> <rsc>
-        [params <param>=<value> [<param>=<value>...]]
-        [meta <attribute>=<value> [<attribute>=<value>...]]"""
-        return self.__conf_object(cmd,*args)
-    def conf_ms(self,cmd,*args):
-        """usage: ms <name> <rsc>
-        [params <param>=<value> [<param>=<value>...]]
-        [meta <attribute>=<value> [<attribute>=<value>...]]"""
-        return self.__conf_object(cmd,*args)
-    def conf_location(self,cmd,*args):
-        """usage: location <id> <rsc> {node_pref|rules}
-
-        node_pref :: <score>: <node>
-
-        rules ::
-          rule [id_spec] [$role=<role>] <score>: <expression>
-          [rule [id_spec] [$role=<role>] <score>: <expression> ...]
-
-        id_spec :: $id=<id> | $id-ref=<id>
-        score :: <number> | <attribute> | [-]inf
-        expression :: <simple_exp> [bool_op <simple_exp> ...]
-        bool_op :: or | and
-        simple_exp :: <attribute> [type:]<binary_op> <value>
-                      | <unary_op> <attribute>
-                      | date <date_expr>
-        type :: string | version | number
-        binary_op :: lt | gt | lte | gte | eq | ne
-        unary_op :: defined | not_defined"""
-        return self.__conf_object(cmd,*args)
-    def conf_colocation(self,cmd,*args):
-        """usage: colocation <id> <score>: <rsc>[:<role>] <rsc>[:<role>]
-        """
-        return self.__conf_object(cmd,*args)
-    def conf_order(self,cmd,*args):
-        """usage: order <id> score-type: <first-rsc>[:<action>] <then-rsc>[:<action>]
-        [symmetrical=<bool>]"""
-        return self.__conf_object(cmd,*args)
-    def conf_property(self,cmd,*args):
-        "usage: property [$id=<set_id>] <option>=<value>"
-        return self.__conf_object(cmd,*args)
-    def conf_rsc_defaults(self,cmd,*args):
-        "usage: rsc_defaults [$id=<set_id>] <option>=<value>"
-        return self.__conf_object(cmd,*args)
-    def conf_op_defaults(self,cmd,*args):
-        "usage: op_defaults [$id=<set_id>] <option>=<value>"
-        return self.__conf_object(cmd,*args)
-    def conf_monitor(self,cmd,*args):
-        "usage: monitor <rsc>[:<role>] <interval>[:<timeout>]"
-        return self.__conf_object(cmd,*args)
-    def end_game(self, no_questions_asked = False):
-        if cib_factory.has_cib_changed():
-            if no_questions_asked or not interactive or \
-                ask("There are changes pending. Do you want to commit them?"):
-                self.commit("commit")
-        cib_factory.reset()
-        wcache.clear()
-
-attr_defaults_missing = {
-}
-def add_missing_attr(node):
-    try:
-        for defaults in attr_defaults_missing[node.tagName]:
-            if not node.hasAttribute(defaults[0]):
-                node.setAttribute(defaults[0],defaults[1])
-    except: pass
-attr_defaults = {
-    "rule": (("boolean-op","and"),),
-    "expression": (("type","string"),),
-}
-def drop_attr_defaults(node, ts = 0):
-    try:
-        for defaults in attr_defaults[node.tagName]:
-            if node.getAttribute(defaults[0]) == defaults[1]:
-                node.removeAttribute(defaults[0])
-    except: pass
-
-def is_element(xmlnode):
-    return xmlnode and xmlnode.nodeType == xmlnode.ELEMENT_NODE
-
-def nameandid(xmlnode,level):
-    if xmlnode.nodeType == xmlnode.ELEMENT_NODE:
-        print level*' ',xmlnode.tagName,xmlnode.getAttribute("id"),xmlnode.getAttribute("name")
-
-def xmltraverse(xmlnode,fun,ts=0):
-    for c in xmlnode.childNodes:
-        if is_element(c):
-            fun(c,ts)
-            xmltraverse(c,fun,ts+1)
-
-def xmltraverse_thin(xmlnode,fun,ts=0):
-    '''
-    Skip elements which may be resources themselves.
-    NB: Call this only on resource (or constraint) nodes, but
-    never on cib or configuration!
-    '''
-    for c in xmlnode.childNodes:
-        if is_element(c) and not c.tagName in ('primitive','group'):
-            xmltraverse_thin(c,fun,ts+1)
-    fun(xmlnode,ts)
-
-def xml_processnodes(xmlnode,node_filter,proc):
-    '''
-    Process with proc all nodes that match filter.
-    '''
-    node_list = []
-    for child in xmlnode.childNodes:
-        if node_filter(child):
-            node_list.append(child)
-        if child.hasChildNodes():
-            xml_processnodes(child,node_filter,proc)
-    if node_list:
-        proc(node_list)
-
-# filter the cib
-def is_whitespace(node):
-    return node.nodeType == node.TEXT_NODE and not node.data.strip()
-def is_comment(node):
-    return node.nodeType == node.COMMENT_NODE
-def is_status_node(node):
-    return is_element(node) and node.tagName == "status"
-
-container_tags = ("group", "clone", "ms", "master")
-clonems_tags = ("clone", "ms", "master")
-resource_tags = ("primitive","group","clone","ms","master")
-constraint_tags = ("rsc_location","rsc_colocation","rsc_order")
-constraint_rsc_refs = ("rsc","with-rsc","first","then")
-children_tags = ("group", "primitive")
-nvpairs_tags = ("meta_attributes", "instance_attributes")
-defaults_tags = ("rsc_defaults","op_defaults")
-precious_attrs = ("id-ref",)
-def is_emptynvpairs(node):
-    if is_element(node) and node.tagName in nvpairs_tags:
-        for a in precious_attrs:
-            if node.getAttribute(a):
-                return False
-        for n in node.childNodes:
-            if is_element(n):
-                return False
-        return True
-    else:
-        return False
-def is_group(node):
-    return is_element(node) \
-        and node.tagName == "group"
-def is_ms(node):
-    return is_element(node) \
-        and node.tagName in ("master","ms")
-def is_clone(node):
-    return is_element(node) \
-        and node.tagName == "clone"
-def is_clonems(node):
-    return is_element(node) \
-        and node.tagName in clonems_tags
-def is_container(node):
-    return is_element(node) \
-        and node.tagName in container_tags
-def is_primitive(node):
-    return is_element(node) \
-        and node.tagName == "primitive"
-def is_resource(node):
-    return is_element(node) \
-        and node.tagName in resource_tags
-def is_child_rsc(node):
-    return is_element(node) \
-        and node.tagName in children_tags
-def is_constraint(node):
-    return is_element(node) \
-        and node.tagName in constraint_tags
-def is_defaults(node):
-    return is_element(node) \
-        and node.tagName in defaults_tags
-def rsc_constraint(rsc_id,cons_node):
-    if not is_element(cons_node):
-        return False
-    for attr in cons_node.attributes.keys():
-        if attr in constraint_rsc_refs \
-                and rsc_id == cons_node.getAttribute(attr):
-            return True
-    for rref in cons_node.getElementsByTagName("resource_ref"):
-        if rsc_id == rref.getAttribute("id"):
-            return True
-    return False
-
-resource_cli_names = olist(["primitive","group","clone","ms","master"])
-constraint_cli_names = olist(["location","colocation","collocation","order"])
-nvset_cli_names = olist(["property","rsc_defaults","op_defaults"])
-op_cli_names = (["monitor", "start", "stop", "migrate_to", "migrate_from","promote","demote","notify"])
-
-def is_resource_cli(s):
-    return s in resource_cli_names
-def is_constraint_cli(s):
-    return s in constraint_cli_names
-
-def sort_container_children(node_list):
-    '''
-    Make sure that attributes's nodes are first, followed by the
-    elements (primitive/group). The order of elements is not
-    disturbed, they are just shifted to end!
-    '''
-    for node in node_list:
-        children = []
-        for c in node.childNodes:
-            if is_element(c) and c.tagName in children_tags:
-                children.append(c)
-        for c in children:
-            node.removeChild(c)
-        for c in children:
-            node.appendChild(c)
-def rmnode(node):
-    if node and node.parentNode:
-        if node.parentNode:
-            node.parentNode.removeChild(node)
-        node.unlink()
-def rmnodes(node_list):
-    for node in node_list:
-        rmnode(node)
-def printid(node_list):
-    for node in node_list:
-        id = node.getAttribute("id")
-        if id: print "node id:",id
-def sanitize_cib(doc):
-    xml_processnodes(doc,is_status_node,rmnodes)
-    #xml_processnodes(doc,is_element,printid)
-    xml_processnodes(doc,is_emptynvpairs,rmnodes)
-    xml_processnodes(doc,is_whitespace,rmnodes)
-    xml_processnodes(doc,is_comment,rmnodes)
-    xml_processnodes(doc,is_container,sort_container_children)
-    xmltraverse(doc,drop_attr_defaults)
-
-def is_simpleconstraint(node):
-    return len(node.getElementsByTagName("resource_ref")) == 0
-def rename_id(node,old_id,new_id):
-    if node.getAttribute("id") == old_id:
-        node.setAttribute("id", new_id)
-def rename_rscref_simple(c_obj,old_id,new_id):
-    c_modified = False
-    for attr in c_obj.node.attributes.keys():
-        if attr in constraint_rsc_refs and \
-                c_obj.node.getAttribute(attr) == old_id:
-            c_obj.node.setAttribute(attr, new_id)
-            c_obj.updated = True
-            c_modified = True
-    return c_modified
-def delete_rscref_simple(c_obj,rsc_id):
-    c_modified = False
-    for attr in c_obj.node.attributes.keys():
-        if attr in constraint_rsc_refs and \
-                c_obj.node.getAttribute(attr) == rsc_id:
-            c_obj.node.removeAttribute(attr)
-            c_obj.updated = True
-            c_modified = True
-    return c_modified
-def rset_uniq(c_obj,d):
-    '''
-    Drop duplicate resource references.
-    '''
-    l = []
-    for rref in c_obj.node.getElementsByTagName("resource_ref"):
-        rsc_id = rref.getAttribute("id")
-        if d[rsc_id] > 1: # drop one
-            l.append(rref)
-            d[rsc_id] -= 1
-    rmnodes(l)
-def delete_rscref_rset(c_obj,rsc_id):
-    '''
-    Drop all reference to rsc_id.
-    '''
-    c_modified = False
-    l = []
-    for rref in c_obj.node.getElementsByTagName("resource_ref"):
-        if rsc_id == rref.getAttribute("id"):
-            l.append(rref)
-            c_obj.updated = True
-            c_modified = True
-    rmnodes(l)
-    l = []
-    for rset in c_obj.node.getElementsByTagName("resource_set"):
-        if len(rset.getElementsByTagName("resource_ref")) == 0:
-            l.append(rset)
-            c_obj.updated = True
-            c_modified = True
-    rmnodes(l)
-    return c_modified
-def rset_convert(c_obj):
-    l = c_obj.node.getElementsByTagName("resource_ref")
-    if len(l) != 2:
-        return # eh?
-    c_obj.modified = True
-    cli = c_obj.repr_cli()
-    newnode = c_obj.cli2node(cli)
-    if newnode:
-        c_obj.node.parentNode.replaceChild(newnode,c_obj.node)
-        c_obj.node.unlink()
-def rename_rscref_rset(c_obj,old_id,new_id):
-    c_modified = False
-    d = {}
-    for rref in c_obj.node.getElementsByTagName("resource_ref"):
-        rsc_id = rref.getAttribute("id")
-        if rsc_id == old_id:
-            rref.setAttribute("id", new_id)
-            rsc_id = new_id
-            c_obj.updated = True
-            c_modified = True
-        if not rsc_id in d:
-            d[rsc_id] = 0
-        else: 
-            d[rsc_id] += 1
-    rset_uniq(c_obj,d)
-    # if only two resource references remained then, to preserve
-    # sanity, convert it to a simple constraint (sigh)
-    cnt = 0
-    for key in d:
-        cnt += d[key]
-    if cnt == 2:
-        rset_convert(c_obj)
-    return c_modified
-def rename_rscref(c_obj,old_id,new_id):
-    if rename_rscref_simple(c_obj,old_id,new_id) or \
-            rename_rscref_rset(c_obj,old_id,new_id):
-        err_buf.info("resource references in %s updated" % c_obj.obj_string())
-def delete_rscref(c_obj,rsc_id):
-    return delete_rscref_simple(c_obj,rsc_id) or \
-        delete_rscref_rset(c_obj,rsc_id)
-def silly_constraint(c_node,rsc_id):
-    '''
-    Remove a constraint from rsc_id to rsc_id.
-    Or an invalid one.
-    '''
-    if c_node.getElementsByTagName("resource_ref"):
-        # it's a resource set
-        # the resource sets have already been uniq-ed
-        return len(c_node.getElementsByTagName("resource_ref")) <= 1
-    cnt = 0  # total count of referenced resources have to be at least two
-    rsc_cnt = 0
-    for attr in c_node.attributes.keys():
-        if attr in constraint_rsc_refs:
-            cnt += 1
-            if c_node.getAttribute(attr) == rsc_id:
-                rsc_cnt += 1
-    if c_node.tagName == "rsc_location":  # locations are never silly
-        return cnt < 1
-    else:
-        return rsc_cnt == 2 or cnt < 2
-
-class IdMgmt(object):
-    '''
-    Make sure that ids are unique.
-    '''
-    def __init__(self):
-        self._id_store = {}
-        self.ok = True # error var
-    def new(self,node,pfx):
-        '''
-        Create a unique id for the xml node.
-        '''
-        name = node.getAttribute("name")
-        if node.tagName == "nvpair":
-            node_id = "%s-%s" % (pfx,name)
-        elif node.tagName == "op":
-            interval = node.getAttribute("interval")
-            if interval:
-                node_id = "%s-%s-%s" % (pfx,name,interval)
-            else:
-                node_id = "%s-%s" % (pfx,name)
-        else:
-            try:
-                hint = hints_list[node.tagName]
-            except: hint = ''
-            if hint:
-                node_id = "%s-%s" % (pfx,hint)
-            else:
-                node_id = "%s" % pfx
-        if self.is_used(node_id):
-            for cnt in range(99): # shouldn't really get here
-                try_id = "%s-%d" % (node_id,cnt)
-                if not self.is_used(try_id):
-                    node_id = try_id
-                    break
-        self.save(node_id)
-        return node_id
-    def check_node(self,node,lvl):
-        node_id = node.getAttribute("id")
-        if not node_id:
-            return
-        if id_in_use(node_id):
-            self.ok = False
-            return
-    def _store_node(self,node,lvl):
-        self.save(node.getAttribute("id"))
-    def _drop_node(self,node,lvl):
-        self.remove(node.getAttribute("id"))
-    def check_xml(self,node):
-        self.ok = True
-        xmltraverse_thin(node,self.check_node)
-        return self.ok
-    def store_xml(self,node):
-        if not self.check_xml(node):
-            return False
-        xmltraverse_thin(node,self._store_node)
-        return True
-    def remove_xml(self,node):
-        xmltraverse_thin(node,self._drop_node)
-    def replace_xml(self,oldnode,newnode):
-        self.remove_xml(oldnode)
-        if not self.store_xml(newnode):
-            self.store_xml(oldnode)
-            return False
-        return True
-    def is_used(self,node_id):
-        return node_id in self._id_store
-    def save(self,node_id):
-        if not node_id: return
-        self._id_store[node_id] = 1
-    def rename(self,old_id,new_id):
-        if not old_id or not new_id: return
-        if not self.is_used(old_id): return
-        if self.is_used(new_id): return
-        self.remove(old_id)
-        self.save(new_id)
-    def remove(self,node_id):
-        if not node_id: return
-        try:
-            del self._id_store[node_id]
-        except KeyError:
-            pass
-    def clear(self):
-        self._id_store = {}
-
-def id_in_use(obj_id):
-    if id_store.is_used(obj_id):
-        id_used_err(obj_id)
-        return True
-    return False
-
-#
-# resource type definition
-#
-def disambiguate_ra_type(s):
-    '''
-    Unravel [class:[provider:]]type
-    '''
-    l = s.split(':')
-    if not l or len(l) > 3:
-        return None
-    if len(l) == 3:
-        return l
-    elif len(l) == 2:
-        ra_class,ra_type = l
-    else:
-        ra_class = "ocf"
-        ra_type = l[0]
-    ra_provider = ''
-    if ra_class == "ocf":
-        pl = ra_providers(ra_type,ra_class)
-        if pl and len(pl) == 1:
-            ra_provider = pl[0]
-        elif not pl:
-            ra_provider = 'heartbeat'
-    return ra_class,ra_provider,ra_type
-def ra_type_validate(s, ra_class, provider, rsc_type):
-    '''
-    Only ocf ra class supports providers.
-    '''
-    if not rsc_type:
-        common_err("bad resource type specification %s"%s)
-        return False
-    if ra_class == "ocf":
-        if not provider:
-            common_err("provider could not be determined for %s"%s)
-            return False
-    else:
-        if provider:
-            common_warn("ra class %s does not support providers"%ra_class)
-            return True
-    return True
-
-req_op_attributes = olist([\
-    "name", \
-    "id", \
-])
-op_attributes = olist([\
-    "interval", \
-    "timeout", \
-    "requires", \
-    "enabled", \
-    "role", \
-    "on-fail", \
-    "start-delay", \
-    "allow-migrate", \
-    "interval-origin", \
-    "record-pending", \
-    "description", \
-])
-
-#
-# CLI parsing utilities
-# WARNING: ugly code ahead (to be replaced some day by a proper
-# yacc parser, if there's such a thing)
-#
-def cli_parse_rsctype(s, pl):
-    '''
-    Parse the resource type.
-    '''
-    ra_class,provider,rsc_type = disambiguate_ra_type(s)
-    if not ra_type_validate(s,ra_class,provider,rsc_type):
-        return None
-    pl.append(["class",ra_class])
-    if ra_class == "ocf":
-        pl.append(["provider",provider])
-    pl.append(["type",rsc_type])
-def is_attribute(p,a):
-    return p.startswith(a + '=')
-def cli_parse_attr_strict(s,pl):
-    '''
-    Parse attributes in the 'p=v' form.
-    '''
-    if s and '=' in s[0]:
-        n,v = s[0].split('=',1)
-        if not n:
-            return
-        pl.append([n,v])
-        cli_parse_attr_strict(s[1:],pl)
-def cli_parse_attr(s,pl):
-    '''
-    Parse attributes in the 'p=v' form.
-    Allow also the 'p' form (no value) unless p is one of the
-    attr_list_keyw words.
-    '''
-    attr_lists_keyw = olist(["params","meta","operations","op","attributes"])
-    if s:
-        if s[0] in attr_lists_keyw:
-            return
-        if '=' in s[0]:
-            n,v = s[0].split('=',1)
-        else:
-            n = s[0]; v = None
-        if not n:
-            return
-        pl.append([n,v])
-        cli_parse_attr(s[1:],pl)
-def is_only_id(pl,keyw):
-    if len(pl) > 1:
-        common_err("%s: only single $id or $id-ref attribute is allowed" % keyw)
-        return False
-    if len(pl) == 1 and pl[0][0] not in ("$id","$id-ref"):
-        common_err("%s: only single $id or $id-ref attribute is allowed" % keyw)
-        return False
-    return True
-time_op_attr = ("timeout")
-def check_operation(pl):
-    op_name = find_value(pl,"name")
-    if not op_name in op_cli_names:
-        common_warn("%s: operation not recognized" % op_name)
-    if op_name == "monitor" and not find_value(pl,"interval"):
-        common_err("monitor requires interval")
-        return False
-    rc = True
-    for a,v in pl:
-        if a in time_op_attr and crm_msec(v) < 0:
-            common_err("%s: bad time in operation %s, attribute %s" % \
-                (v,op_name,a))
-            rc = False
-    return rc
-def parse_resource(s):
-    el_type = s[0].lower()
-    if el_type == "master": # ugly kludge :(
-        el_type = "ms"
-    attr_lists_keyw = olist(["params","meta"])
-    cli_list = []
-    # the head
-    head = []
-    head.append(["id",s[1]])
-    i = 3
-    if el_type == "primitive":
-        cli_parse_rsctype(s[2],head)
-        if not find_value(head,"type"):
-            syntax_err(s[2:], context = "primitive")
-            return False
-    else:
-        cl = []
-        cl.append(s[2])
-        if el_type == "group":
-            while i < len(s):
-                if s[i] in attr_lists_keyw:
-                    break
-                elif is_attribute(s[i],"description"):
-                    break
-                else:
-                    cl.append(s[i])
-                    i += 1 # skip to the next token
-        head.append(["$children",cl])
-    try:  # s[i] may be out of range
-        if is_attribute(s[i],"description"):
-            cli_parse_attr(s[i:i+1],head)
-            i += 1 # skip to the next token
-    except: pass
-    cli_list.append([el_type,head])
-    # the rest
-    state = 0 # 1: reading operations; 2: operations read
-    while len(s) > i+1:
-        pl = []
-        keyw = s[i].lower()
-        if keyw in attr_lists_keyw:
-            if state == 1:
-                state = 2
-        elif el_type == "primitive" and state == 0 and keyword_cmp(keyw, "operations"):
-            state = 1
-        elif el_type == "primitive" and state <= 1 and keyword_cmp(keyw, "op"):
-            if state == 0:
-                state = 1
-            pl.append(["name",s[i+1]])
-        else:
-            syntax_err(s[i:], context = 'primitive')
-            return False
-        if keyword_cmp(keyw, "op"):
-            if len(s) > i+2:
-                cli_parse_attr(s[i+2:],pl)
-            if not check_operation(pl):
-                return False
-        else:
-            cli_parse_attr(s[i+1:],pl)
-            if len(pl) == 0:
-                syntax_err(s[i:], context = 'primitive')
-                return False
-        if keyword_cmp(keyw, "operations") and not is_only_id(pl,keyw):
-            return False
-        i += len(pl)+1
-        # interval is obligatory for ops, supply 0 if not there
-        if keyword_cmp(keyw, "op") and not find_value(pl,"interval"):
-            pl.append(["interval","0"])
-        cli_list.append([keyw,pl])
-    if len(s) > i:
-        syntax_err(s[i:], context = 'primitive')
-        return False
-    return cli_list
-def parse_op(s):
-    if len(s) != 3:
-        syntax_err(s, context = s[0])
-        return False
-    cli_list = []
-    head_pl = []
-    # this is an op
-    cli_list.append(["op",head_pl])
-    if not cli_parse_rsc_role(s[1],head_pl):
-        return False
-    if not cli_parse_op_times(s[2],head_pl):
-        return False
-    # rename rsc-role to role
-    for i in range(len(head_pl)):
-        if head_pl[i][0] == "rsc-role":
-            head_pl[i][0] = "role"
-            break
-    # add the operation name
-    head_pl.append(["name",s[0]])
-    return cli_list
-
-score_type = {'advisory': '0','mandatory': 'INFINITY'}
-def cli_parse_score(score,pl,noattr = False):
-    if score.endswith(':'):
-        score = score.rstrip(':')
-    else:
-        syntax_err(score, context = 'score')
-        return False
-    if score in score_type:
-        pl.append(["score",score_type[score]])
-    elif re.match("^[+-]?(inf|infinity|INFINITY|[[0-9]+)$",score):
-        score = score.replace("infinity","INFINITY")
-        score = score.replace("inf","INFINITY")
-        pl.append(["score",score])
-    elif score:
-        if noattr:
-            common_err("attribute not allowed for score in orders")
-            return False
-        else:
-            pl.append(["score-attribute",score])
-    return True
-boolean_ops = olist(['or','and'])
-binary_ops = olist(['lt','gt','lte','gte','eq','ne'])
-binary_types = ('string' , 'version' , 'number')
-unary_ops = olist(['defined','not_defined'])
-def is_binary_op(s):
-    l = s.split(':')
-    if len(l) == 2:
-        return l[0] in binary_types and l[1] in binary_ops
-    elif len(l) == 1:
-        return l[0] in binary_ops
-    else:
-        return False
-def cli_parse_binary_op(s,pl):
-    l = s.split(':')
-    if len(l) == 2:
-        pl.append(["type",l[0]])
-        pl.append(["operation",l[1]])
-    else:
-        pl.append(["operation",l[0]])
-def cli_parse_expression(s,pl):
-    if len(s) > 1 and s[0] in unary_ops:
-        pl.append(["operation",s[0]])
-        pl.append(["attribute",s[1]])
-    elif len(s) > 2 and is_binary_op(s[1]):
-        pl.append(["attribute",s[0]])
-        cli_parse_binary_op(s[1],pl)
-        pl.append(["value",s[2]])
-    else:
-        return False
-    return True
-simple_date_ops = olist(['lt','gt'])
-date_ops = olist(['lt','gt','in_range','date_spec'])
-date_spec_names = '''hours monthdays weekdays yearsdays months \
-    weeks years weekyears moon'''.split()
-in_range_attrs = ('start','end')
-def cli_parse_dateexpr(s,pl):
-    if len(s) < 3:
-        return False
-    if s[1] not in date_ops:
-        return False
-    pl.append(["operation",s[1]])
-    if s[1] in simple_date_ops:
-        pl.append([keyword_cmp(s[1], 'lt') and "end" or "start",s[2]])
-        return True
-    cli_parse_attr_strict(s[2:],pl)
-    return True
-def parse_rule(s):
-    if not keyword_cmp(s[0], "rule"):
-        syntax_err(s,context = "rule")
-        return 0,None
-    rule_list = []
-    head_pl = []
-    rule_list.append([s[0].lower(),head_pl])
-    i = 1
-    cli_parse_attr_strict(s[i:],head_pl)
-    i += len(head_pl)
-    if find_value(head_pl,"$id-ref"):
-        return i,rule_list
-    if not cli_parse_score(s[i],head_pl):
-        return i,None
-    i += 1
-    bool_op = ''
-    while len(s) > i+1:
-        pl = []
-        if keyword_cmp(s[i], "date"):
-            fun = cli_parse_dateexpr
-            elem = "date_expression"
-        else:
-            fun = cli_parse_expression
-            elem = "expression"
-        if not fun(s[i:],pl):
-            syntax_err(s[i:],context = "rule")
-            return i,None
-        rule_list.append([elem,pl])
-        i += len(pl)
-        if find_value(pl, "type"):
-            i -= 1 # reduce no of tokens by one if there was "type:op"
-        if elem == "date_expression":
-            i += 1 # increase no of tokens by one if it was date expression
-        if len(s) > i and s[i] in boolean_ops:
-            if bool_op and not keyword_cmp(bool_op, s[i]):
-                common_err("rule contains different bool operations: %s" % ' '.join(s))
-                return i,None
-            else:
-                bool_op = s[i].lower()
-                i += 1
-        if len(s) > i and keyword_cmp(s[i], "rule"):
-            break
-    if bool_op and not keyword_cmp(bool_op, 'and'):
-        head_pl.append(["boolean-op",bool_op])
-    return i,rule_list
-def parse_location(s):
-    cli_list = []
-    head_pl = []
-    head_pl.append(["id",s[1]])
-    head_pl.append(["rsc",s[2]])
-    cli_list.append([s[0].lower(),head_pl])
-    if len(s) == 5 and not keyword_cmp(s[3], "rule"): # the short node preference form
-        if not cli_parse_score(s[3],head_pl):
-            return False
-        head_pl.append(["node",s[4]])
-        return cli_list
-    i = 3
-    while i < len(s):
-        numtoks,l = parse_rule(s[i:])
-        if not l:
-            return False
-        cli_list += l
-        i += numtoks
-    if len(s) < i:
-        syntax_err(s[i:],context = "location")
-        return False
-    return cli_list
-
-def cli_opt_symmetrical(p,pl):
-    if not p:
-        return True
-    pl1 = []
-    cli_parse_attr([p],pl1)
-    if len(pl1) != 1 or not find_value(pl1,"symmetrical"):
-        syntax_err(p,context = "order")
-        return False
-    pl += pl1
-    return True
-roles_names = ('Stopped', 'Started', 'Master', 'Slave')
-def cli_parse_rsc_role(s,pl,attr_pfx = ''):
-    l = s.split(':')
-    pl.append([attr_pfx+"rsc",l[0]])
-    if len(l) == 2:
-        if l[1] not in roles_names:
-            bad_def_err("resource role",s)
-            return False
-        pl.append([attr_pfx+"rsc-role",l[1]])
-    elif len(l) > 2:
-        bad_def_err("resource role",s)
-        return False
-    return True
-def cli_parse_op_times(s,pl):
-    l = s.split(':')
-    pl.append(["interval",l[0]])
-    if len(l) == 2:
-        pl.append(["timeout",l[1]])
-    elif len(l) > 2:
-        bad_def_err("op times",s)
-        return False
-    return True
-
-class ResourceSet(object):
-    '''
-    Constraint resource set parser. Parses sth like:
-    a ( b c:start ) d:Master e ...
-    Appends one or more lists to cli_list.
-    Lists are in form:
-        list :: ["resource_set",set_pl]
-        set_pl :: [["sequential","false"], ["action"|"role",action|role],
-            ["resource_ref",["id",rsc]], ...]
-        (the first two elements of set_pl are optional)
-    Action/role change makes a new resource set.
-    '''
-    def __init__(self,type,s,cli_list):
-        self.type = type
-        self.valid_q = (type == "order") and actions_names or roles_names
-        self.q_attr = (type == "order") and "action" or "role"
-        self.tokens = s
-        self.cli_list = cli_list
-        self.reset_set()
-        self.sequential = True
-        self.fix_parentheses()
-    def fix_parentheses(self):
-        newtoks = []
-        for p in self.tokens:
-            if p.startswith('(') and len(p) > 1:
-                newtoks.append('(')
-                newtoks.append(p[1:])
-            elif p.endswith(')') and len(p) > 1:
-                newtoks.append(p[0:len(p)-1])
-                newtoks.append(')')
-            else:
-                newtoks.append(p)
-        self.tokens = newtoks
-    def reset_set(self):
-        self.set_pl = []
-        self.prev_q = ''  # previous qualifier (action or role)
-        self.curr_attr = ''  # attribute (action or role)
-    def save_set(self):
-        if not self.set_pl:
-            return
-        if self.curr_attr:
-            self.set_pl.insert(0,[self.curr_attr,self.prev_q])
-        if not self.sequential:
-            self.set_pl.insert(0,["sequential","false"])
-        self.cli_list.append(["resource_set",self.set_pl])
-        self.reset_set()
-    def splitrsc(self,p):
-        l = p.split(':')
-        return (len(l) == 1) and [p,''] or l
-    def parse(self):
-        tokpos = -1
-        for p in self.tokens:
-            tokpos += 1
-            if p == "_rsc_set_":
-                continue # a degenerate resource set
-            if p == '(':
-                if self.set_pl: # save the set before
-                    self.save_set()
-                self.sequential = False
-                continue
-            if p == ')':
-                if self.sequential:  # no '('
-                    syntax_err(self.tokens[tokpos:],context = self.type)
-                    return False
-                if not self.set_pl:  # empty sets not allowed
-                    syntax_err(self.tokens[tokpos:],context = self.type)
-                    return False
-                self.save_set()
-                self.sequential = True
-                continue
-            rsc,q = self.splitrsc(p)
-            if q != self.prev_q: # one set can't have different roles/actions
-                self.save_set()
-                self.prev_q = q
-            if q:
-                if q not in self.valid_q:
-                    common_err("%s: invalid %s in %s" % (q,self.q_attr,self.type))
-                    return False
-                if not self.curr_attr:
-                    self.curr_attr = self.q_attr
-            else:
-                self.curr_attr = ''
-            self.set_pl.append(["resource_ref",["id",rsc]])
-        if not self.sequential: # no ')'
-            syntax_err(self.tokens[tokpos:],context = self.type)
-            return False
-        if self.set_pl: # save the final set
-            self.save_set()
-        return True
-
-def parse_colocation(s):
-    cli_list = []
-    head_pl = []
-    type = s[0]
-    if type == "collocation": # another ugly :(
-        type = "colocation"
-    cli_list.append([type,head_pl])
-    if len(s) < 5:
-        syntax_err(s,context = "colocation")
-        return False
-    head_pl.append(["id",s[1]])
-    if not cli_parse_score(s[2],head_pl):
-        return False
-    if len(s) == 5:
-        if not cli_parse_rsc_role(s[3],head_pl):
-            return False
-        if not cli_parse_rsc_role(s[4],head_pl,'with-'):
-            return False
-    else:
-        resource_set_obj = ResourceSet(type,s[3:],cli_list)
-        if not resource_set_obj.parse():
-            return False
-    return cli_list
-actions_names = ( 'start', 'promote', 'demote', 'stop')
-def cli_parse_rsc_action(s,pl,rsc_pos):
-    l = s.split(':')
-    pl.append([rsc_pos,l[0]])
-    if len(l) == 2:
-        if l[1] not in actions_names:
-            bad_def_err("resource action",s)
-            return False
-        pl.append([rsc_pos+"-action",l[1]])
-    elif len(l) > 1:
-        bad_def_err("resource action",s)
-        return False
-    return True
-
-def parse_order(s):
-    cli_list = []
-    head_pl = []
-    type = "order"
-    cli_list.append([s[0],head_pl])
-    if len(s) < 5:
-        syntax_err(s,context = "order")
-        return False
-    head_pl.append(["id",s[1]])
-    if not cli_parse_score(s[2],head_pl,noattr = True):
-        return False
-    # save symmetrical for later (if it exists)
-    symm = ""
-    if is_attribute(s[len(s)-1],"symmetrical"):
-        symm = s.pop()
-    if len(s) == 5:
-        if not cli_parse_rsc_action(s[3],head_pl,'first'):
-            return False
-        if not cli_parse_rsc_action(s[4],head_pl,'then'):
-            return False
-    else:
-        resource_set_obj = ResourceSet(type,s[3:],cli_list)
-        if not resource_set_obj.parse():
-            return False
-    if not cli_opt_symmetrical(symm,head_pl):
-        return False
-    return cli_list
-
-def parse_constraint(s):
-    if keyword_cmp(s[0], "location"):
-        return parse_location(s)
-    elif s[0] in olist(["colocation","collocation"]):
-        return parse_colocation(s)
-    elif keyword_cmp(s[0], "order"):
-        return parse_order(s)
-def parse_property(s):
-    cli_list = []
-    head_pl = []
-    cli_list.append([s[0],head_pl])
-    cli_parse_attr(s[1:],head_pl)
-    if len(head_pl) < 0 or len(s) > len(head_pl)+1:
-        syntax_err(s, context = s[0])
-        return False
-    return cli_list
-def cli_parse_uname(s, pl):
-    l = s.split(':')
-    if not l or len(l) > 2:
-        return None
-    pl.append(["uname",l[0]])
-    if len(l) == 2:
-        pl.append(["type",l[1]])
-def parse_node(s):
-    cli_list = []
-    # the head
-    head = []
-    # optional $id
-    id = ''
-    opt_id_l = []
-    i = 1
-    cli_parse_attr_strict(s[i:],opt_id_l)
-    if opt_id_l:
-        id = find_value(opt_id_l,"$id")
-        i += 1
-    # uname[:type]
-    cli_parse_uname(s[i],head)
-    uname = find_value(head,"uname")
-    if not uname:
-        return False
-    head.append(["id",id and id or uname])
-    # drop type if default
-    type = find_value(head,"type")
-    if type == CibNode.default_type:
-        head.remove(["type",type])
-    cli_list.append([s[0],head])
-    if len(s) == i:
-        return cli_list
-    # the rest
-    i += 1
-    try:  # s[i] may be out of range
-        if is_attribute(s[i],"description"):
-            cli_parse_attr(s[i:i+1],head)
-            i += 1 # skip to the next token
-    except: pass
-    keyw = CibNode.node_attributes_keyw # some day there may be more than one
-    while len(s) > i+1:
-        if not keyword_cmp(s[i], keyw):
-            syntax_err(s[i:], context = 'node')
-            return False
-        pl = []
-        cli_parse_attr(s[i+1:],pl)
-        if len(pl) == 0:
-            syntax_err(s[i:], context = 'node')
-            return False
-        i += len(pl)+1
-        cli_list.append([keyw,pl])
-    if len(s) > i:
-        syntax_err(s[i:], context = 'node')
-        return False
-    return cli_list
-
-def parse_cli(s):
-    '''
-    Input: a list of tokens (or a CLI format string).
-    Return: a list of items; each item is a tuple
-        with two members: a string (tag) and a nvpairs or
-        attributes dict.
-    '''
-    if type(s) == type(u''):
-        s = s.encode('ascii')
-    if type(s) == type(''):
-        try: s = shlex.split(s)
-        except ValueError, msg:
-            common_err(msg)
-            return False
-    while '\n' in s:
-        s.remove('\n')
-    if s and s[0].startswith('#'):
-        return None
-    if len(s) > 1 and s[0] in nvset_cli_names:
-        return parse_property(s)
-    if len(s) > 1 and keyword_cmp(s[0], "node"):
-        return parse_node(s)
-    if len(s) < 3: # we want at least two tokens
-        syntax_err(s)
-        return False
-    if is_resource_cli(s[0]):
-        return parse_resource(s)
-    elif is_constraint_cli(s[0]):
-        return parse_constraint(s)
-    elif keyword_cmp(s[0], "monitor"):
-        return parse_op(s)
-    else:
-        syntax_err(s)
-        return False
-
-#
-# XML generate utilities
-#
-hints_list = {
-    "instance_attributes": "instance_attributes",
-    "meta_attributes": "meta_attributes",
-    "operations": "ops",
-    "rule": "rule",
-    "expression": "expression",
-}
-match_list = {
-    "node": ("uname"),
-    "crm_config": (),
-    "rsc_defaults": (),
-    "op_defaults": (),
-    "cluster_property_set": (),
-    "instance_attributes": (),
-    "meta_attributes": (),
-    "operations": (),
-    "nvpair": ("name",),
-    "op": ("name","interval"),
-    "rule": ("score","score-attribute","role"),
-    "expression": ("attribute","operation","value"),
-}
-def set_id_used_attr(node):
-    node.setAttribute("__id_used", "Yes")
-def is_id_used_attr(node):
-    return node.getAttribute("__id_used") == "Yes"
-def remove_id_used_attr(node,lvl):
-    if is_element(node) and is_id_used_attr(node):
-        node.removeAttribute("__id_used")
-def remove_id_used_attributes(node):
-    if node:
-        xmltraverse(node, remove_id_used_attr)
-def lookup_node(node,oldnode,location_only = False):
-    '''
-    Find a child of oldnode which matches node.
-    '''
-    #print "lookup:",node.tagName,node.getAttribute("id"),oldnode.tagName,oldnode.getAttribute("id")
-    if not oldnode:
-        return None
-    try:
-        attr_list = match_list[node.tagName]
-    except KeyError:
-        attr_list = []
-    for c in oldnode.childNodes:
-        if not is_element(c):
-            continue
-        if not location_only and is_id_used_attr(c):
-            continue
-        #print "checking:",c.tagName,c.getAttribute("id")
-        if node.tagName == c.tagName:
-            failed = False
-            for a in attr_list:
-                if node.getAttribute(a) != c.getAttribute(a):
-                    failed = True
-                    break
-            if not failed:
-                #print "found:",c.tagName,c.getAttribute("id")
-                return c
-    return None
-
-def set_id(node,oldnode,id_hint,id_required = True):
-    '''
-    Set the id attribute for the node.
-    Procedure:
-    - if the node already contains "id", keep it
-    - if the old node contains "id", copy that
-    - if neither is true, then create a new one using id_hint
-      (exception: if not id_required, then no new id is generated)
-    Finally, save the new id in id_store.
-    '''
-    old_id = None
-    new_id = node.getAttribute("id")
-    if oldnode and oldnode.getAttribute("id"):
-        old_id = oldnode.getAttribute("id")
-    if not new_id:
-        new_id = old_id
-    if not new_id:
-        if id_required:
-            new_id = id_store.new(node,id_hint)
-    else:
-        id_store.save(new_id)
-    if new_id:
-        node.setAttribute("id",new_id)
-        if oldnode and old_id == new_id:
-            set_id_used_attr(oldnode)
-
-def mkxmlsimple(e,oldnode,id_hint):
-    '''
-    Create an xml node from the (name,dict) pair. The name is the
-    name of the element. The dict contains a set of attributes.
-    '''
-    node = cib_factory.createElement(e[0])
-    for n,v in e[1]:
-        if n == "$children": # this one's skipped
-            continue
-        if n == "operation":
-            v = v.lower()
-        if n.startswith('$'):
-            n = n.lstrip('$')
-        if (type(v) != type('') and type(v) != type(u'')) \
-                or v: # skip empty strings
-            node.setAttribute(n,v)
-    id_ref = node.getAttribute("id-ref")
-    if id_ref:
-        id_ref_2 = cib_factory.resolve_id_ref(e[0],id_ref)
-        node.setAttribute("id-ref",id_ref_2)
-    else:
-        set_id(node,lookup_node(node,oldnode),id_hint)
-    return node
-
-def find_value(pl,name):
-    for n,v in pl:
-        if n == name:
-            return v
-    return None
-def find_operation(rsc_node,name,interval):
-    op_node_l = rsc_node.getElementsByTagName("operations")
-    for ops in op_node_l:
-        for c in ops.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName != "op":
-                continue
-            if c.getAttribute("name") == name \
-                    and c.getAttribute("interval") == interval:
-                return c
-def mkxmlnvpairs(e,oldnode,id_hint):
-    '''
-    Create xml from the (name,dict) pair. The name is the name of
-    the element. The dict contains a set of nvpairs. Stuff such
-    as instance_attributes.
-    NB: Other tags not containing nvpairs are fine if the dict is empty.
-    '''
-    node = cib_factory.createElement(e[0])
-    match_node = lookup_node(node,oldnode)
-    #if match_node:
-        #print "found nvpairs set:",match_node.tagName,match_node.getAttribute("id")
-    id_ref = find_value(e[1],"$id-ref")
-    if id_ref:
-        id_ref_2 = cib_factory.resolve_id_ref(e[0],id_ref)
-        node.setAttribute("id-ref",id_ref_2)
-        if e[0] != "operations":
-            return node # id_ref is the only attribute (if not operations)
-        e[1].remove(["$id-ref",id_ref])
-    v = find_value(e[1],"$id")
-    if v:
-        node.setAttribute("id",v)
-        e[1].remove(["$id",v])
-    else:
-        if e[0] == "operations": # operations don't need no id
-            set_id(node,match_node,id_hint,id_required = False)
-        else:
-            set_id(node,match_node,id_hint)
-    try:
-        hint = hints_list[e[0]]
-    except: hint = ''
-    hint = hint and "%s_%s" % (id_hint,hint) or id_hint
-    nvpair_pfx = node.getAttribute("id") or hint
-    for n,v in e[1]:
-        nvpair = cib_factory.createElement("nvpair")
-        node.appendChild(nvpair)
-        nvpair.setAttribute("name",n)
-        if v != None:
-            nvpair.setAttribute("value",v)
-        set_id(nvpair,lookup_node(nvpair,match_node),nvpair_pfx)
-    return node
-
-def mkxmlop(e,oldnode,id_hint):
-    '''
-    Create an operation xml from the (name,dict) pair.
-    '''
-    node = cib_factory.createElement(e[0])
-    inst_attr = []
-    for n,v in e[1]:
-        if n in req_op_attributes + op_attributes:
-            node.setAttribute(n,v)
-        else:
-            inst_attr.append([n,v])
-    tmp = cib_factory.createElement("operations")
-    oldops = lookup_node(tmp,oldnode) # first find old operations
-    oldop = lookup_node(node,oldops)
-    set_id(node,oldop,id_hint)
-    if inst_attr:
-        e = ["instance_attributes",inst_attr]
-        nia = mkxmlnvpairs(e,oldop,node.getAttribute("id"))
-        node.appendChild(nia)
-    return node
-
-def mkxmldate(e,oldnode,id_hint):
-    '''
-    Create a date_expression xml from the (name,dict) pair.
-    '''
-    node = cib_factory.createElement(e[0])
-    operation = find_value(e[1],"operation").lower()
-    node.setAttribute("operation", operation)
-    old_date = lookup_node(node,oldnode) # first find old date element
-    set_id(node,old_date,id_hint)
-    date_spec_attr = []
-    for n,v in e[1]:
-        if n in date_ops or n == "operation":
-            continue
-        elif n in in_range_attrs:
-            node.setAttribute(n,v)
-        else:
-            date_spec_attr.append([n,v])
-    if not date_spec_attr:
-        return node
-    elem = operation == "date_spec" and "date_spec" or "duration"
-    tmp = cib_factory.createElement(elem)
-    old_date_spec = lookup_node(tmp,old_date) # first find old date element
-    set_id(tmp,old_date_spec,id_hint)
-    for n,v in date_spec_attr:
-        tmp.setAttribute(n,v)
-    node.appendChild(tmp)
-    return node
-
-def mkxmlrsc_set(e,oldnode,id_hint):
-    '''
-    Create a resource_set xml from the (name,dict) pair.
-    '''
-    node = cib_factory.createElement(e[0])
-    old_rsc_set = lookup_node(node,oldnode) # first find old date element
-    set_id(node,old_rsc_set,id_hint)
-    for ref in e[1]:
-        if ref[0] == "resource_ref":
-            ref_node = cib_factory.createElement(ref[0])
-            ref_node.setAttribute(ref[1][0],ref[1][1])
-            node.appendChild(ref_node)
-        elif ref[0] in ("sequential", "action", "role"):
-            node.setAttribute(ref[0], ref[1])
-    return node
-
-conv_list = odict()
-conv_list["params"] = "instance_attributes"
-conv_list["meta"] = "meta_attributes"
-conv_list["property"] = "cluster_property_set"
-conv_list["rsc_defaults"] = "meta_attributes"
-conv_list["op_defaults"] = "meta_attributes"
-conv_list["attributes"] = "instance_attributes"
-conv_list["operations"] = "operations"
-conv_list["op"] = "op"
-
-def mkxmlnode(e,oldnode,id_hint):
-    '''
-    Create xml from the (name,dict) pair. The name is the name of
-    the element. The dict contains either a set of nvpairs or a
-    set of attributes. The id is either generated or copied if
-    found in the provided xml. Stuff such as instance_attributes.
-    '''
-    if e[0] in conv_list:
-        e[0] = conv_list[e[0]]
-    if e[0] in ("instance_attributes","meta_attributes","operations","cluster_property_set"):
-        return mkxmlnvpairs(e,oldnode,id_hint)
-    elif e[0] == "op":
-        return mkxmlop(e,oldnode,id_hint)
-    elif e[0] == "date_expression":
-        return mkxmldate(e,oldnode,id_hint)
-    elif e[0] == "resource_set":
-        return mkxmlrsc_set(e,oldnode,id_hint)
-    else:
-        return mkxmlsimple(e,oldnode,id_hint)
-
-def new_cib():
-    doc = xml.dom.minidom.Document()
-    cib = doc.createElement("cib")
-    doc.appendChild(cib)
-    configuration = doc.createElement("configuration")
-    cib.appendChild(configuration)
-    crm_config = doc.createElement("crm_config")
-    configuration.appendChild(crm_config)
-    rsc_defaults = doc.createElement("rsc_defaults")
-    configuration.appendChild(rsc_defaults)
-    op_defaults = doc.createElement("op_defaults")
-    configuration.appendChild(op_defaults)
-    nodes = doc.createElement("nodes")
-    configuration.appendChild(nodes)
-    resources = doc.createElement("resources")
-    configuration.appendChild(resources)
-    constraints = doc.createElement("constraints")
-    configuration.appendChild(constraints)
-    return doc,cib,crm_config,rsc_defaults,op_defaults,nodes,resources,constraints
-def mk_topnode(doc, tag):
-    "Get configuration element or create/append if there's none."
-    try:
-        e = doc.getElementsByTagName(tag)[0]
-    except:
-        e = doc.createElement(tag)
-        conf = doc.getElementsByTagName("configuration")[0]
-        if conf:
-            conf.appendChild(e)
-        else:
-            return None
-    return e
-def get_conf_elem(doc, tag):
-    try:
-        return doc.getElementsByTagName(tag)[0]
-    except:
-        return None
-def xmlparse(f):
-    try:
-        doc = xml.dom.minidom.parse(f)
-    except xml.parsers.expat.ExpatError,msg:
-        common_err("cannot parse xml: %s" % msg)
-        return None
-    return doc
-def read_cib(fun, params = None):
-    doc = fun(params)
-    if not doc:
-        return doc,None
-    cib = doc.childNodes[0]
-    if not is_element(cib) or cib.tagName != "cib":
-        cib_no_elem_err("cib")
-        return doc,None
-    return doc,cib
-
-def set_nvpair(set_node,name,value):
-    n_id = set_node.getAttribute("id")
-    for c in set_node.childNodes:
-        if is_element(c) and c.getAttribute("name") == name:
-            c.setAttribute("value",value)
-            return
-    np = cib_factory.createElement("nvpair")
-    np.setAttribute("name",name)
-    np.setAttribute("value",value)
-    new_id = id_store.new(np,n_id)
-    np.setAttribute("id",new_id)
-    set_node.appendChild(np)
-
-def xml_cmp(n, m, show = False):
-    rc = hash(n.toxml()) == hash(m.toxml())
-    if not rc and show and user_prefs.get_debug():
-        print "original:",n.toprettyxml()
-        print "processed:",m.toprettyxml()
-    return hash(n.toxml()) == hash(m.toxml())
-
-def show_unrecognized_elems(doc):
-    try:
-        conf = doc.getElementsByTagName("configuration")[0]
-    except:
-        common_warn("CIB has no configuration element")
-        return
-    for topnode in conf.childNodes:
-        if not is_element(topnode):
-            continue
-        if is_defaults(topnode):
-            continue
-        if not topnode.tagName in cib_topnodes:
-            common_warn("unrecognized CIB element %s" % c.tagName)
-            continue
-        for c in topnode.childNodes:
-            if not is_element(topnode):
-                continue
-            if not c.tagName in cib_object_map:
-                common_warn("unrecognized CIB element %s" % c.tagName)
-
-def get_interesting_nodes(node,nodes):
-    for c in node.childNodes:
-        if is_element(c) and c.tagName in cib_object_map:
-            nodes.append(c)
-        get_interesting_nodes(c,nodes)
-    return nodes
-
-def filter_on_tag(nl,tag):
-    return [node for node in nl if node.tagName == tag]
-def nodes(node_list):
-    return filter_on_tag(node_list,"node")
-def primitives(node_list):
-    return filter_on_tag(node_list,"primitive")
-def groups(node_list):
-    return filter_on_tag(node_list,"group")
-def clones(node_list):
-    return filter_on_tag(node_list,"clone")
-def mss(node_list):
-    return filter_on_tag(node_list,"master")
-def constraints(node_list):
-    return filter_on_tag(node_list,"rsc_location") \
-        + filter_on_tag(node_list,"rsc_colocation") \
-        + filter_on_tag(node_list,"rsc_order")
-def properties(node_list):
-    return filter_on_tag(node_list,"cluster_property_set") \
-        + filter_on_tag(node_list,"rsc_defaults") \
-        + filter_on_tag(node_list,"op_defaults")
-def processing_sort(nl):
-    '''
-    It's usually important to process cib objects in this order,
-    i.e. simple objects first.
-    '''
-    return nodes(nl) + primitives(nl) + groups(nl) + mss(nl) + clones(nl) \
-        + constraints(nl) + properties(nl)
-
-def obj_cmp(obj1,obj2):
-    return cmp(obj1.obj_id,obj2.obj_id)
-def filter_on_type(cl,obj_type):
-    if type(cl[0]) == type([]):
-        l = [cli_list for cli_list in cl if cli_list[0][0] == obj_type]
-        l.sort(cmp = cmp)
-    else:
-        l = [obj for obj in cl if obj.obj_type == obj_type]
-        l.sort(cmp = obj_cmp)
-    return l
-def nodes_cli(cl):
-    return filter_on_type(cl,"node")
-def primitives_cli(cl):
-    return filter_on_type(cl,"primitive")
-def groups_cli(cl):
-    return filter_on_type(cl,"group")
-def clones_cli(cl):
-    return filter_on_type(cl,"clone")
-def mss_cli(cl):
-    return filter_on_type(cl,"ms") + filter_on_type(cl,"master")
-def constraints_cli(node_list):
-    return filter_on_type(node_list,"location") \
-        + filter_on_type(node_list,"colocation") \
-        + filter_on_type(node_list,"collocation") \
-        + filter_on_type(node_list,"order")
-def properties_cli(cl):
-    return filter_on_type(cl,"property") \
-        + filter_on_type(cl,"rsc_defaults") \
-        + filter_on_type(cl,"op_defaults")
-def ops_cli(cl):
-    return filter_on_type(cl,"op")
-def processing_sort_cli(cl):
-    '''
-    Return the given list in this order:
-    nodes, primitives, groups, ms, clones, constraints, rest
-    Both a list of objects (CibObject) and list of cli
-    representations accepted.
-    '''
-    return nodes_cli(cl) + primitives_cli(cl) + groups_cli(cl) + mss_cli(cl) + clones_cli(cl) \
-        + constraints_cli(cl) + properties_cli(cl) + ops_cli(cl)
-
-def referenced_resources_cli(cli_list):
-    id_list = []
-    head = cli_list[0]
-    obj_type = head[0]
-    if not is_constraint_cli(obj_type):
-        return []
-    if obj_type == "location":
-        id_list.append(find_value(head[1],"rsc"))
-    elif len(cli_list) > 1: # resource sets
-        for l in cli_list[1][1]:
-            if l[0] == "resource_ref":
-                id_list.append(l[1][1])
-    elif obj_type == "colocation":
-        id_list.append(find_value(head[1],"rsc"))
-        id_list.append(find_value(head[1],"with-rsc"))
-    elif obj_type == "order":
-        id_list.append(find_value(head[1],"first"))
-        id_list.append(find_value(head[1],"then"))
-    return id_list
-
-#
-# CLI format generation utilities (from XML)
-#
-def cli_format(pl,format):
-    if format:
-        return ' \\\n\t'.join(pl)
-    else:
-        return ' '.join(pl)
-def nvpair_format(n,v):
-    return v == None and cli_display.attr_name(n) \
-        or '%s="%s"'%(cli_display.attr_name(n),cli_display.attr_value(v))
-def cli_pairs(pl):
-    'Return a string of name="value" pairs (passed in a list of pairs).'
-    l = []
-    for n,v in pl:
-        l.append(nvpair_format(n,v))
-    return ' '.join(l)
-def nvpairs2list(node, add_id = False):
-    '''
-    Convert nvpairs to a list of pairs.
-    The id attribute is normally skipped, since they tend to be
-    long and therefore obscure the relevant content. For some
-    elements, however, they are included (e.g. properties).
-    '''
-    pl = []
-    # if there's id-ref, there can be then _only_ id-ref
-    value = node.getAttribute("id-ref")
-    if value:
-        pl.append(["$id-ref",value])
-        return pl
-    if add_id or \
-            (not node.childNodes and len(node.attributes) == 1):
-        value = node.getAttribute("id")
-        if value:
-            pl.append(["$id",value])
-    for c in node.childNodes:
-        if not is_element(c):
-            continue
-        if c.tagName == "attributes":
-            pl = nvpairs2list(c)
-        name = c.getAttribute("name")
-        if "value" in c.attributes.keys():
-            value = c.getAttribute("value")
-        else:
-            value = None
-        pl.append([name,value])
-    return pl
-def op2list(node):
-    pl = []
-    action = ""
-    for name in node.attributes.keys():
-        if name == "name":
-            action = node.getAttribute(name)
-        elif name != "id": # skip the id
-            pl.append([name,node.getAttribute(name)])
-    if not action:
-        common_err("op is invalid (no name)")
-    return action,pl
-def op_instattr(node):
-    pl = []
-    for c in node.childNodes:
-        if not is_element(c):
-            continue
-        if c.tagName != "instance_attributes":
-            common_err("only instance_attributes are supported in operations")
-        else:
-            pl += nvpairs2list(c)
-    return pl
-def cli_op(node):
-    action,pl = op2list(node)
-    if not action:
-        return ""
-    pl += op_instattr(node)
-    return "%s %s %s" % (cli_display.keyword("op"),action,cli_pairs(pl))
-def cli_operations(node,format = True):
-    l = []
-    node_id = node.getAttribute("id")
-    s = ''
-    if node_id:
-        s = '$id="%s"' % node_id
-    idref = node.getAttribute("id-ref")
-    if idref:
-        s = '%s $id-ref="%s"' % (s,idref)
-    if s:
-        l.append("%s %s" % (cli_display.keyword("operations"),s))
-    for c in node.childNodes:
-        if is_element(c) and c.tagName == "op":
-            l.append(cli_op(c))
-    return cli_format(l,format)
-def date_exp2cli(node):
-    l = []
-    operation = node.getAttribute("operation")
-    l.append(cli_display.keyword("date"))
-    l.append(cli_display.keyword(operation))
-    if operation in simple_date_ops:
-        value = node.getAttribute(operation == 'lt' and "end" or "start")
-        l.append('"%s"' % cli_display.attr_value(value))
-    else:
-        if operation == 'in_range':
-            for name in in_range_attrs:
-                v = node.getAttribute(name)
-                if v:
-                    l.append(nvpair_format(name,v))
-        for c in node.childNodes:
-            if is_element(c) and c.tagName in ("duration","date_spec"):
-                pl = []
-                for name in c.attributes.keys():
-                    if name != "id":
-                        pl.append([name,c.getAttribute(name)])
-                l.append(cli_pairs(pl))
-    return ' '.join(l)
-def binary_op_format(op):
-    l = op.split(':')
-    if len(l) == 2:
-        return "%s:%s" % (l[0], cli_display.keyword(l[1]))
-    else:
-        return cli_display.keyword(op)
-def exp2cli(node):
-    operation = node.getAttribute("operation")
-    type = node.getAttribute("type")
-    if type:
-        operation = "%s:%s" % (type, operation)
-    attribute = node.getAttribute("attribute")
-    value = node.getAttribute("value")
-    if not value:
-        return "%s %s" % (binary_op_format(operation),attribute)
-    else:
-        return "%s %s %s" % (attribute,binary_op_format(operation),value)
-def get_score(node):
-    score = node.getAttribute("score")
-    if not score:
-        score = node.getAttribute("score-attribute")
-    else:
-        if score.find("INFINITY") >= 0:
-            score = score.replace("INFINITY","inf")
-    return score + ":"
-def cli_rule(node):
-    s = []
-    node_id = node.getAttribute("id")
-    if node_id:
-        s.append('$id="%s"' % node_id)
-    else:
-        idref = node.getAttribute("id-ref")
-        if idref:
-            return '$id-ref="%s"' % idref
-    rsc_role = node.getAttribute("role")
-    if rsc_role:
-        s.append('$role="%s"' % rsc_role)
-    s.append(cli_display.score(get_score(node)))
-    bool_op = node.getAttribute("boolean-op")
-    if not bool_op:
-        bool_op = "and"
-    exp = []
-    for c in node.childNodes:
-        if not is_element(c):
-            continue
-        if c.tagName == "date_expression":
-            exp.append(date_exp2cli(c))
-        elif c.tagName == "expression":
-            exp.append(exp2cli(c))
-    expression = (" %s "%cli_display.keyword(bool_op)).join(exp)
-    return "%s %s" % (' '.join(s),expression)
-def node_head(node):
-    obj_type = cib_object_map[node.tagName][0]
-    node_id = node.getAttribute("id")
-    uname = node.getAttribute("uname")
-    s = cli_display.keyword(obj_type)
-    if node_id != uname:
-        s = '%s $id="%s"' % (s, node_id)
-    s = '%s %s' % (s, cli_display.id(uname))
-    type = node.getAttribute("type")
-    if type != CibNode.default_type:
-        s = '%s:%s' % (s, type)
-    return s
-def cli_add_description(node,l):
-    desc = node.getAttribute("description")
-    if desc:
-        l.append(nvpair_format("description",desc))
-def primitive_head(node):
-    obj_type = cib_object_map[node.tagName][0]
-    node_id = node.getAttribute("id")
-    ra_type = node.getAttribute("type")
-    ra_class = node.getAttribute("class")
-    ra_provider = node.getAttribute("provider")
-    s1 = s2 = ''
-    if ra_class:
-        s1 = "%s:"%ra_class
-    if ra_provider:
-        s2 = "%s:"%ra_provider
-    s = cli_display.keyword(obj_type)
-    id = cli_display.id(node_id)
-    return "%s %s %s" % (s, id, ''.join((s1,s2,ra_type)))
-
-def cont_head(node):
-    obj_type = cib_object_map[node.tagName][0]
-    node_id = node.getAttribute("id")
-    children = []
-    for c in node.childNodes:
-        if not is_element(c):
-            continue
-        if (obj_type == "group" and is_primitive(c)) or \
-                is_child_rsc(c):
-            children.append(cli_display.rscref(c.getAttribute("id")))
-        elif obj_type in clonems_tags and is_child_rsc(c):
-            children.append(cli_display.rscref(c.getAttribute("id")))
-    s = cli_display.keyword(obj_type)
-    id = cli_display.id(node_id)
-    return "%s %s %s" % (s, id, ' '.join(children))
-def location_head(node):
-    obj_type = cib_object_map[node.tagName][0]
-    node_id = node.getAttribute("id")
-    rsc = cli_display.rscref(node.getAttribute("rsc"))
-    s = cli_display.keyword(obj_type)
-    id = cli_display.id(node_id)
-    s = "%s %s %s"%(s,id,rsc)
-    pref_node = node.getAttribute("node")
-    score = cli_display.score(get_score(node))
-    if pref_node:
-        return "%s %s %s" % (s,score,pref_node)
-    else:
-        return s
-def mkrscrole(node,n):
-    rsc = cli_display.rscref(node.getAttribute(n))
-    rsc_role = node.getAttribute(n + "-role")
-    if rsc_role:
-        return "%s:%s"%(rsc,rsc_role)
-    else:
-        return rsc
-def mkrscaction(node,n):
-    rsc = cli_display.rscref(node.getAttribute(n))
-    rsc_action = node.getAttribute(n + "-action")
-    if rsc_action:
-        return "%s:%s"%(rsc,rsc_action)
-    else:
-        return rsc
-def rsc_set_constraint(node,obj_type):
-    col = []
-    cnt = 0
-    for n in node.getElementsByTagName("resource_set"):
-        sequential = True
-        if n.getAttribute("sequential") == "false":
-            sequential = False
-        if not sequential:
-            col.append("(")
-        role = n.getAttribute("role")
-        action = n.getAttribute("action")
-        for r in n.getElementsByTagName("resource_ref"):
-            rsc = cli_display.rscref(r.getAttribute("id"))
-            q = (obj_type == "colocation") and role or action
-            col.append(q and "%s:%s"%(rsc,q) or rsc)
-            cnt += 1
-        if not sequential:
-            col.append(")")
-    if cnt <= 2: # a degenerate thingie
-        col.insert(0,"_rsc_set_")
-    return col
-def two_rsc_constraint(node,obj_type):
-    col = []
-    if obj_type == "colocation":
-        col.append(mkrscrole(node,"rsc"))
-        col.append(mkrscrole(node,"with-rsc"))
-    else:
-        col.append(mkrscaction(node,"first"))
-        col.append(mkrscaction(node,"then"))
-    return col
-def simple_constraint_head(node):
-    obj_type = cib_object_map[node.tagName][0]
-    node_id = node.getAttribute("id")
-    s = cli_display.keyword(obj_type)
-    id = cli_display.id(node_id)
-    score = cli_display.score(get_score(node))
-    if node.getElementsByTagName("resource_set"):
-        col = rsc_set_constraint(node,obj_type)
-    else:
-        col = two_rsc_constraint(node,obj_type)
-    symm = node.getAttribute("symmetrical")
-    if symm:
-        col.append("symmetrical=%s"%symm)
-    return "%s %s %s %s" % (s,id,score,' '.join(col))
-
-def get_tag_by_id(node,tag,id):
-    "Find a doc node which matches tag and id."
-    for n in node.getElementsByTagName(tag):
-        if n.getAttribute("id") == id:
-            return n
-    return None
-def get_status_node(n):
-    try: n = n.parentNode
-    except: return None
-    if n.tagName != "node_state":
-        return get_status_node(n)
-    return n.getAttribute("id")
-def get_status_ops(status_node,rsc,op,interval,node = ''):
-    '''
-    Find a doc node which matches the operation. interval set to
-    "-1" means to lookup an operation with non-zero interval (for
-    monitors). Empty interval means any interval is fine.
-    '''
-    l = []
-    for n in status_node.childNodes:
-        if not is_element(n) or n.tagName != "node_state":
-            continue
-        if node and n.getAttribute("id") != node:
-            continue
-        for r in n.getElementsByTagName("lrm_resource"):
-            if r.getAttribute("id") != rsc:
-                continue
-            for o in r.getElementsByTagName("lrm_rsc_op"):
-                if o.getAttribute("operation") != op:
-                    continue
-                if (interval == "") or \
-                  (interval == "-1" and o.getAttribute("interval") != "0") or \
-                  (interval != "" and o.getAttribute("interval") == interval):
-                    l.append(o)
-    return l
-
-class CibStatus(object):
-    '''
-    CIB status management
-    '''
-    def __init__(self):
-        self.origin = "live"
-        self.status_node = None
-        self.doc = None
-        self.cib = None
-        self.modified = False
-        self.node_changes = {}
-        self.op_changes = {}
-    def _cib_path(self,source):
-        if source[0:7] == "shadow:":
-            return shadowfile(source[7:])
-        else:
-            return source
-    def _load_cib(self,source):
-        if source == "live":
-            doc,cib = read_cib(cibdump2doc)
-        else:
-            doc,cib = read_cib(file2doc,self._cib_path(source))
-        return doc,cib
-    def _load(self,source):
-        doc,cib = self._load_cib(source)
-        if not doc:
-            return False
-        status = get_conf_elem(doc, "status")
-        if not status:
-            return False
-        self.doc,self.cib = doc,cib
-        self.status_node = status
-        self.modified = False
-        self.node_changes = {}
-        self.op_changes = {}
-        return True
-    def status_node_list(self):
-        if not self.status_node and not self._load(self.origin):
-            return
-        return [x.getAttribute("id") for x in self.doc.getElementsByTagName("node_state")]
-    def status_rsc_list(self):
-        if not self.status_node and not self._load(self.origin):
-            return
-        rsc_list = [x.getAttribute("id") for x in self.doc.getElementsByTagName("lrm_resource")]
-        # how to uniq?
-        d = {}
-        for e in rsc_list:
-            d[e] = 0
-        return d.keys()
-    def load(self,source):
-        '''
-        Load the status section from the given source. The source
-        may be cluster ("live"), shadow CIB, or CIB in a file.
-        '''
-        if not self._load(source):
-            common_err("the cib contains no status")
-            return False
-        self.origin = source
-        return True
-    def save(self,dest = None):
-        '''
-        Save the modified status section to a file/shadow. If the
-        file exists, then it must be a cib file and the status
-        section is replaced with our status section. If the file
-        doesn't exist, then our section and some (?) configuration
-        is saved.
-        '''
-        if not self.modified:
-            common_info("apparently you didn't modify status")
-            return False
-        if (not dest and self.origin == "live") or dest == "live":
-            common_warn("cannot save status to the cluster")
-            return False
-        doc,cib = self.doc,self.cib
-        if dest:
-            dest_path = self._cib_path(dest)
-            if os.path.isfile(dest_path):
-                doc,cib = self._load_cib(dest)
-                if not doc or not cib:
-                    common_err("%s exists, but no cib inside" % dest)
-                    return False
-        else:
-            dest_path = self._cib_path(self.origin)
-        if doc != self.doc:
-            status = get_conf_elem(doc, "status")
-            rmnode(status)
-            cib.appendChild(doc.importNode(self.status_node,1))
-        xml = doc.toprettyxml(user_prefs.xmlindent)
-        try: f = open(dest_path,"w")
-        except IOError, msg:
-            common_err(msg)
-            return False
-        f.write(xml)
-        f.close()
-        return True
-    def get_status(self):
-        '''
-        Return the status section node.
-        '''
-        if not self.status_node and not self._load(self.origin):
-            return None
-        return self.status_node
-    def list_changes(self):
-        '''
-        Dump a set of changes done.
-        '''
-        if not self.modified:
-            return True
-        for node in self.node_changes:
-            print node,self.node_changes[node]
-        for op in self.op_changes:
-            print op,self.op_changes[op]
-        return True
-    def show(self):
-        '''
-        Page the "pretty" XML of the status section.
-        '''
-        if not self.status_node and not self._load(self.origin):
-            return
-        page_string(self.status_node.toprettyxml(user_prefs.xmlindent))
-        return True
-    def edit_node(self,node,state):
-        '''
-        Modify crmd, expected, and join attributes of node_state
-        to set the node's state to online, offline, or unclean.
-        '''
-        if not self.status_node and not self._load(self.origin):
-            return
-        node_node = get_tag_by_id(self.status_node,"node_state",node)
-        if not node_node:
-            common_err("node %s not found" % node)
-            return False
-        if state == "online":
-            node_node.setAttribute("crmd","online")
-            node_node.setAttribute("expected","member")
-            node_node.setAttribute("join","member")
-        elif state == "offline":
-            node_node.setAttribute("crmd","offline")
-            node_node.setAttribute("expected","")
-        elif state == "unclean":
-            node_node.setAttribute("crmd","offline")
-            node_node.setAttribute("expected","member")
-        else:
-            common_err("unknown state %s" % state)
-            return False
-        self.node_changes[node] = state
-        self.modified = True
-        return True
-    def edit_op(self,op,rsc,rc,op_status,node = ''):
-        '''
-        Set rc-code and op-status in the lrm_rsc_op status
-        section element.
-        '''
-        if not self.status_node and not self._load(self.origin):
-            return
-        l_op = op
-        l_int = ""
-        if op == "probe":
-            l_op = "monitor"
-            l_int = "0"
-        elif op == "monitor":
-            l_int = "-1"
-        elif op[0:8] == "monitor:":
-            l_op = "monitor"
-            l_int = op[8:]
-        op_nodes = get_status_ops(self.status_node,rsc,l_op,l_int,node)
-        if len(op_nodes) == 0:
-            common_err("operation %s not found" % op)
-            return False
-        elif len(op_nodes) > 1:
-            nodelist = [get_status_node(x) for x in op_nodes]
-            common_err("operation %s found at %s" % (op,' '.join(nodelist)))
-            return False
-        op_node = op_nodes[0]
-        if not node:
-            node = get_status_node(op_node)
-        prev_rc = op_node.getAttribute("rc-code")
-        op_node.setAttribute("rc-code",rc)
-        self.op_changes[node+":"+rsc+":"+op] = "rc="+rc
-        if op_status:
-            op_node.setAttribute("op-status",op_status)
-            self.op_changes[node+":"+rsc+":"+op] += "," "op-status="+op_status
-        op_node.setAttribute("last-run",str(int(time.time())))
-        if rc != prev_rc:
-            op_node.setAttribute("last-rc-change",str(int(time.time())))
-        self.modified = True
-        return True
-
-class CibObjectSet(object):
-    '''
-    Edit or display a set of cib objects.
-    repr() for objects representation and
-    save() used to store objects into internal structures
-    are defined in subclasses.
-    '''
-    def __init__(self, *args):
-        self.obj_list = []
-    def _open_url(self,src):
-        import urllib
-        try:
-            return urllib.urlopen(src)
-        except:
-            pass
-        if src == "-":
-            return sys.stdin
-        try:
-            return open(src)
-        except:
-            pass
-        common_err("could not open %s" % src)
-        return False
-    def init_aux_lists(self):
-        '''
-        Before edit, initialize two auxiliary lists which will
-        hold a list of objects to be removed and a list of
-        objects which were created. Then, we can create a new
-        object list which will match the current state of
-        affairs, i.e. the object set after the last edit.
-        '''
-        self.remove_objs = copy.copy(self.obj_list)
-        self.add_objs = []
-    def recreate_obj_list(self):
-        '''
-        Recreate obj_list: remove deleted objects and add
-        created objects
-        '''
-        for obj in self.remove_objs:
-            self.obj_list.remove(obj)
-        self.obj_list += self.add_objs
-        rmlist = []
-        for obj in self.obj_list:
-            if obj.invalid:
-                rmlist.append(obj)
-        for obj in rmlist:
-            self.obj_list.remove(obj)
-    def edit_save(self,s,erase = False):
-        '''
-        Save string s to a tmp file. Invoke editor to edit it.
-        Parse/save the resulting file. In case of syntax error,
-        allow user to reedit. If erase is True, erase the CIB
-        first.
-        If no changes are done, return silently.
-        '''
-        tmp = str2tmp(s)
-        if not tmp:
-            return False
-        filehash = hash(s)
-        rc = False
-        while True:
-            if edit_file(tmp) != 0:
-                break
-            try: f = open(tmp,'r')
-            except IOError, msg:
-                common_err(msg)
-                break
-            s = ''.join(f)
-            f.close()
-            if hash(s) == filehash: # file unchanged
-                rc = True
-                break
-            if erase:
-                cib_factory.erase()
-            if not self.save(s):
-                if ask("Do you want to edit again?"):
-                    continue
-            rc = True
-            break
-        try: os.unlink(tmp)
-        except: pass
-        return rc
-    def edit(self):
-        if batch:
-            common_info("edit not allowed in batch mode")
-            return False
-        cli_display.set_no_pretty()
-        s = self.repr()
-        cli_display.reset_no_pretty()
-        return self.edit_save(s)
-    def save_to_file(self,fname):
-        if fname == "-":
-            f = sys.stdout
-        else:
-            if not batch and os.access(fname,os.F_OK):
-                if not ask("File %s exists. Do you want to overwrite it?"%fname):
-                    return False
-            try: f = open(fname,"w")
-            except IOError, msg:
-                common_err(msg)
-                return False
-        rc = True
-        cli_display.set_no_pretty()
-        s = self.repr()
-        cli_display.reset_no_pretty()
-        if s:
-            f.write(s)
-            f.write('\n')
-        elif self.obj_list:
-            rc = False
-        if f != sys.stdout:
-            f.close()
-        return rc
-    def show(self):
-        s = self.repr()
-        if not s:
-            if self.obj_list: # objects could not be displayed
-                return False
-            else:
-                return True
-        page_string(s)
-    def import_file(self,method,fname):
-        if not cib_factory.is_cib_sane():
-            return False
-        if method == "replace":
-            if interactive and cib_factory.has_cib_changed():
-                if not ask("This operation will erase all changes. Do you want to proceed?"):
-                    return False
-            cib_factory.erase()
-        f = self._open_url(fname)
-        if not f:
-            return False
-        s = ''.join(f)
-        if f != sys.stdin:
-            f.close()
-        return self.save(s)
-    def repr(self):
-        '''
-        Return a string with objects's representations (either
-        CLI or XML).
-        '''
-        return ''
-    def save(self,s):
-        '''
-        For each object:
-            - try to find a corresponding object in obj_list
-            - if not found: create new
-            - if found: replace the object in the obj_list with
-              the new object
-        See below for specific implementations.
-        '''
-        pass
-    def verify2(self):
-        '''
-        Test objects for sanity. This is about semantics.
-        '''
-        rc = 0
-        for obj in self.obj_list:
-            rc |= obj.check_sanity()
-        return rc
-    def lookup_cli(self,cli_list):
-        for obj in self.obj_list:
-            if obj.matchcli(cli_list):
-                return obj
-    def lookup(self,xml_obj_type,obj_id):
-        for obj in self.obj_list:
-            if obj.match(xml_obj_type,obj_id):
-                return obj
-    def drop_remaining(self):
-        'Any remaining objects in obj_list are deleted.'
-        l = [x.obj_id for x in self.remove_objs]
-        return cib_factory.delete(*l)
-
-def mkset_obj(*args):
-    if args and args[0] == "xml":
-        obj = lambda: CibObjectSetRaw(*args[1:])
-    else:
-        obj = lambda: CibObjectSetCli(*args)
-    return obj()
-
-class CibObjectSetCli(CibObjectSet):
-    '''
-    Edit or display a set of cib objects (using cli notation).
-    '''
-    def __init__(self, *args):
-        CibObjectSet.__init__(self, *args)
-        self.obj_list = cib_factory.mkobj_list("cli",*args)
-    def repr(self):
-        "Return a string containing cli format of all objects."
-        if not self.obj_list:
-            return ''
-        return '\n'.join(obj.repr_cli() \
-            for obj in processing_sort_cli(self.obj_list))
-    def process(self,cli_list):
-        '''
-        Create new objects or update existing ones.
-        '''
-        obj = self.lookup_cli(cli_list)
-        if obj:
-            rc = obj.update_from_cli(cli_list) != False
-            self.remove_objs.remove(obj)
-        else:
-            new_obj = cib_factory.create_from_cli(cli_list)
-            rc = new_obj != None
-            if rc:
-                self.add_objs.append(new_obj)
-        return rc
-    def save(self,s):
-        '''
-        Save a user supplied cli format configuration.
-        On errors user is typically asked to review the
-        configuration (for instance on editting).
-
-        On syntax error (return code 1), no changes are done, but
-        on semantic errors (return code 2), some changes did take
-        place so object list must be updated properly.
-
-        Finally, once syntax check passed, there's no way back,
-        all changes are applied to the current configuration.
-
-        TODO: Implement undo configuration changes.
-        '''
-        global lineno
-        l = []
-        rc = True
-        save_lineno = lineno
-        lineno = 0
-        for cli_text in lines2cli(s):
-            lineno += 1
-            cli_list = parse_cli(cli_text)
-            if cli_list:
-                l.append(cli_list)
-            elif cli_list == False:
-                rc = False
-        lineno = save_lineno
-        # we can't proceed if there was a syntax error, but we
-        # can ask the user to fix problems
-        if not rc:
-            return rc
-        self.init_aux_lists()
-        if l:
-            for cli_list in processing_sort_cli(l):
-                if self.process(cli_list) == False:
-                    rc = False
-        if not self.drop_remaining():
-            # this is tricky, we don't know what was removed!
-            # it could happen that the user dropped a resource
-            # which was running and therefore couldn't be removed
-            rc = False
-        self.recreate_obj_list()
-        return rc
-
-class CibObjectSetRaw(CibObjectSet):
-    '''
-    Edit or display one or more CIB objects (XML).
-    '''
-    def __init__(self, *args):
-        CibObjectSet.__init__(self, *args)
-        self.obj_list = cib_factory.mkobj_list("xml",*args)
-    def repr(self):
-        "Return a string containing xml of all objects."
-        doc = cib_factory.objlist2doc(self.obj_list)
-        s = doc.toprettyxml(user_prefs.xmlindent)
-        doc.unlink()
-        return s
-    def repr_configure(self):
-        '''
-        Return a string containing xml of configure and its
-        children.
-        '''
-        doc = cib_factory.objlist2doc(self.obj_list)
-        conf_node = doc.getElementsByTagName("configuration")[0]
-        s = conf_node.toprettyxml(user_prefs.xmlindent)
-        doc.unlink()
-        return s
-    def process(self,node):
-        if not cib_factory.is_cib_sane():
-            return False
-        obj = self.lookup(node.tagName,node.getAttribute("id"))
-        if obj:
-            rc = obj.update_from_node(node) != False
-            self.remove_objs.remove(obj)
-        else:
-            new_obj = cib_factory.create_from_node(node)
-            rc = new_obj != None
-            if rc:
-                self.add_objs.append(new_obj)
-        return rc
-    def save(self,s):
-        try:
-            doc = xml.dom.minidom.parseString(s)
-        except xml.parsers.expat.ExpatError,msg:
-            cib_parse_err(msg)
-            return False
-        rc = True
-        sanitize_cib(doc)
-        show_unrecognized_elems(doc)
-        newnodes = get_interesting_nodes(doc,[])
-        self.init_aux_lists()
-        if newnodes:
-            for node in processing_sort(newnodes):
-                if not self.process(node):
-                    rc = False
-        if not self.drop_remaining():
-            rc = False
-        doc.unlink()
-        self.recreate_obj_list()
-        return rc
-    def verify(self):
-        if not self.obj_list:
-            return True
-        cli_display.set_no_pretty()
-        rc = pipe_string(cib_verify,self.repr())
-        cli_display.reset_no_pretty()
-        return rc in (0,1)
-    def ptest(self, nograph, scores, verbosity):
-        if not cib_factory.is_cib_sane():
-            return False
-        ptest = "ptest -X -%s" % verbosity.upper()
-        if scores:
-            ptest = "%s -s" % ptest
-        if user_prefs.dotty and not nograph:
-            fd,tmpfile = mkstemp()
-            ptest = "%s -D %s" % (ptest,tmpfile)
-        else:
-            tmpfile = None
-        doc = cib_factory.objlist2doc(self.obj_list)
-        cib = doc.childNodes[0]
-        status = cib_status.get_status()
-        if not status:
-            common_err("no status section found")
-            return False
-        cib.appendChild(doc.importNode(status,1))
-        pipe_string(ptest,doc.toprettyxml())
-        doc.unlink()
-        if tmpfile:
-            p = subprocess.Popen("%s %s" % (user_prefs.dotty,tmpfile), shell=True, bufsize=0, stdin=None, stdout=None, stderr=None, close_fds=True)
-            common_info("starting %s to show transition graph"%user_prefs.dotty)
-            tmpfiles.append(tmpfile)
-        else:
-            if not nograph:
-                common_info("install graphviz to see a transition graph")
-        return True
-
-class CibObject(object):
-    '''
-    The top level object of the CIB. Resources and constraints.
-    '''
-    state_fmt = "%16s %-8s%-8s%-8s%-8s%-8s%-4s"
-    def __init__(self,xml_obj_type,obj_id = None):
-        if not xml_obj_type in cib_object_map:
-            unsupported_err(xml_obj_type)
-            return
-        self.obj_type = cib_object_map[xml_obj_type][0]
-        self.parent_type = cib_object_map[xml_obj_type][2]
-        self.xml_obj_type = xml_obj_type
-        self.origin = "" # where did it originally come from?
-        self.nocli = False # we don't support this one
-        self.updated = False # was the object updated
-        self.invalid = False # the object has been invalidated (removed)
-        self.moved = False # the object has been moved (from/to a container)
-        self.recreate = False # constraints to be recreated
-        self.parent = None # object superior (group/clone/ms)
-        self.children = [] # objects inferior
-        if obj_id:
-            if not self.mknode(obj_id):
-                self = None # won't do :(
-        else:
-            self.obj_id = None
-            self.node = None
-    def dump_state(self):
-        'Print object status'
-        print self.state_fmt % \
-            (self.obj_id,self.origin,self.updated,self.moved,self.invalid, \
-            self.parent and self.parent.obj_id or "", \
-            len(self.children))
-    def repr_cli(self,node = None,format = True):
-        '''
-        CLI representation for the node. Defined in subclasses.
-        '''
-        return ''
-    def cli2node(self,cli,oldnode = None):
-        '''
-        Convert CLI representation to a DOM node.
-        Defined in subclasses.
-        '''
-        return None
-    def save_xml(self,node):
-        self.obj_id = node.getAttribute("id")
-        self.node = node
-        return self.cli_use_validate()
-    def mknode(self,obj_id):
-        if not cib_factory.is_cib_sane():
-            return False
-        if id_in_use(obj_id):
-            return False
-        if self.xml_obj_type in defaults_tags:
-            tag = "meta_attributes"
-        else:
-            tag = self.xml_obj_type
-        self.node = cib_factory.createElement(tag)
-        self.obj_id = obj_id
-        self.node.setAttribute("id",self.obj_id)
-        self.origin = "user"
-        return True
-    def mkcopy(self):
-        '''
-        Create a new object with the same obj_id and obj_type
-        (for the purpose of CibFactory.delete_objects)
-        '''
-        obj_copy = CibObject(self.xml_obj_type)
-        obj_copy.obj_id = self.obj_id
-        obj_copy.obj_type = self.obj_type
-        return obj_copy
-    def can_be_renamed(self):
-        '''
-        Return False if this object can't be renamed.
-        '''
-        if is_rsc_running(self.obj_id):
-            common_err("cannot rename a running resource (%s)" % self.obj_id)
-            return False
-        if not is_live_cib() and self.node.tagName == "node":
-            common_err("cannot rename nodes")
-            return False
-        return True
-    def attr_exists(self,attr):
-        if not attr in self.node.attributes.keys():
-            no_attribute_err(attr,self.obj_id)
-            return False
-        return True
-    def cli_use_validate(self):
-        '''
-        Check validity of the object, as we know it. It may
-        happen that we don't recognize a construct, but that the
-        object is still valid for the CRM. In that case, the
-        object is marked as "CLI read only", i.e. we will neither
-        convert it to CLI nor try to edit it in that format.
-
-        The validation procedure:
-        we convert xml to cli and then back to xml. If the two
-        xml representations match then we can understand the xml.
-        '''
-        if not self.node:
-            return True
-        if not self.attr_exists("id"):
-            return False
-        cli_display.set_no_pretty()
-        cli_text = self.repr_cli()
-        cli_display.reset_no_pretty()
-        if not cli_text:
-            return False
-        xml2 = self.cli2node(cli_text)
-        if not xml2:
-            return False
-        rc = xml_cmp(self.node, xml2, show = True)
-        xml2.unlink()
-        return rc
-    def check_sanity(self):
-        '''
-        Right now, this is only for primitives.
-        '''
-        return 0
-    def matchcli(self,cli_list):
-        head = cli_list[0]
-        return self.obj_type == head[0] \
-            and self.obj_id == find_value(head[1],"id")
-    def match(self,xml_obj_type,obj_id):
-        return self.xml_obj_type == xml_obj_type and self.obj_id == obj_id
-    def obj_string(self):
-        return "%s:%s" % (self.obj_type,self.obj_id)
-    def reset_updated(self):
-        self.updated = False
-        self.moved = False
-        self.recreate = False
-        for child in self.children:
-            child.reset_updated()
-    def propagate_updated(self):
-        if self.parent:
-            self.parent.updated = self.updated
-            self.parent.propagate_updated()
-    def update_links(self):
-        '''
-        Update the structure links for the object (self.children,
-        self.parent). Update also the dom nodes, if necessary.
-        '''
-        self.children = []
-        if self.obj_type not in container_tags:
-            return
-        for c in self.node.childNodes:
-            if is_child_rsc(c):
-                child = cib_factory.find_object_for_node(c)
-                if not child:
-                    missing_obj_err(c)
-                    continue
-                child.parent = self
-                self.children.append(child)
-                if not c.isSameNode(child.node):
-                    rmnode(child.node)
-                    child.node = c
-    def update_from_cli(self,cli_list):
-        'Update ourselves from the cli intermediate.'
-        if not cib_factory.is_cib_sane():
-            return False
-        if not cib_factory.verify_cli(cli_list):
-            return False
-        oldnode = self.node
-        id_store.remove_xml(oldnode)
-        newnode = self.cli2node(cli_list)
-        if not newnode:
-            id_store.store_xml(oldnode)
-            return False
-        if xml_cmp(oldnode,newnode):
-            newnode.unlink()
-            return True # the new and the old versions are equal
-        self.node = newnode
-        if user_prefs.is_check_always() \
-                and self.check_sanity() > 1:
-            id_store.remove_xml(newnode)
-            id_store.store_xml(oldnode)
-            self.node = oldnode
-            newnode.unlink()
-            return False
-        oldnode.parentNode.replaceChild(newnode,oldnode)
-        cib_factory.adjust_children(self,cli_list)
-        oldnode.unlink()
-        self.updated = True
-        self.propagate_updated()
-        return True
-    def update_from_node(self,node):
-        'Update ourselves from a doc node.'
-        if not node:
-            return False
-        if not cib_factory.is_cib_sane():
-            return False
-        oldxml = self.node
-        newxml = node
-        if xml_cmp(oldxml,newxml):
-            return True # the new and the old versions are equal
-        if not id_store.replace_xml(oldxml,newxml):
-            return False
-        oldxml.unlink()
-        self.node = cib_factory.doc.importNode(newxml,1)
-        cib_factory.topnode[self.parent_type].appendChild(self.node)
-        self.update_links()
-        self.updated = True
-        self.propagate_updated()
-    def top_parent(self):
-        '''Return the top parent or self'''
-        if self.parent:
-            return self.parent.top_parent()
-        else:
-            return self
-    def find_child_in_node(self,child):
-        for c in self.node.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName == child.obj_type and \
-                    c.getAttribute("id") == child.obj_id:
-                return c
-        return None
-    def filter(self,*args):
-        "Filter objects."
-        if not args:
-            return True
-        if args[0] == "NOOBJ":
-            return False
-        if args[0] == "changed":
-            return self.updated or self.origin == "user"
-        return self.obj_id in args
-
-def mk_cli_list(cli):
-    'Sometimes we get a string and sometimes a list.'
-    if type(cli) == type('') or type(cli) == type(u''):
-        return parse_cli(cli)
-    else:
-        return cli
-
-class CibNode(CibObject):
-    '''
-    Node and node's attributes.
-    '''
-    default_type = "normal"
-    node_attributes_keyw = "attributes"
-    def repr_cli(self,node = None,format = True):
-        '''
-        We assume that uname is unique.
-        '''
-        if not node:
-            node = self.node
-        l = []
-        l.append(node_head(node))
-        cli_add_description(node,l)
-        for c in node.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName == "instance_attributes":
-                l.append("%s %s" % \
-                    (cli_display.keyword("attributes"), \
-                    cli_pairs(nvpairs2list(c))))
-        return cli_format(l,format)
-    def cli2node(self,cli,oldnode = None):
-        cli_list = mk_cli_list(cli)
-        if not cli_list:
-            return None
-        if not oldnode:
-            oldnode = self.node
-        head = copy.copy(cli_list[0])
-        head[0] = backtrans[head[0]]
-        obj_id = find_value(head[1],"$id")
-        if not obj_id:
-            obj_id = find_value(head[1],"uname")
-        if not obj_id:
-            return None
-        type = find_value(head[1],"type")
-        if not type:
-            type = self.default_type
-            head[1].append(["type",type])
-        headnode = mkxmlsimple(head,cib_factory.topnode[cib_object_map[self.xml_obj_type][2]],'node')
-        id_hint = headnode.getAttribute("id")
-        for e in cli_list[1:]:
-            n = mkxmlnode(e,oldnode,id_hint)
-            headnode.appendChild(n)
-        remove_id_used_attributes(cib_factory.topnode[cib_object_map[self.xml_obj_type][2]])
-        return headnode
-
-class CibPrimitive(CibObject):
-    '''
-    Primitives.
-    '''
-    def repr_cli(self,node = None,format = True):
-        if not node:
-            node = self.node
-        l = []
-        l.append(primitive_head(node))
-        cli_add_description(node,l)
-        for c in node.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName == "instance_attributes":
-                l.append("%s %s" % \
-                    (cli_display.keyword("params"), \
-                    cli_pairs(nvpairs2list(c))))
-            elif c.tagName == "meta_attributes":
-                l.append("%s %s" % \
-                    (cli_display.keyword("meta"), \
-                    cli_pairs(nvpairs2list(c))))
-            elif c.tagName == "operations":
-                l.append(cli_operations(c,format))
-        return cli_format(l,format)
-    def cli2node(self,cli,oldnode = None):
-        '''
-        Convert a CLI description to DOM node.
-        Try to preserve as many ids as possible in case there's
-        an old XML version.
-        '''
-        cli_list = mk_cli_list(cli)
-        if not cli_list:
-            return None
-        if not oldnode:
-            oldnode = self.node
-        head = copy.copy(cli_list[0])
-        head[0] = backtrans[head[0]]
-        headnode = mkxmlsimple(head,oldnode,'rsc')
-        id_hint = headnode.getAttribute("id")
-        operations = None
-        for e in cli_list[1:]:
-            n = mkxmlnode(e,oldnode,id_hint)
-            if keyword_cmp(e[0], "operations"):
-                operations = n
-            if not keyword_cmp(e[0], "op"):
-                headnode.appendChild(n)
-            else:
-                if not operations:
-                    operations = mkxmlnode(["operations",{}],oldnode,id_hint)
-                    headnode.appendChild(operations)
-                operations.appendChild(n)
-        remove_id_used_attributes(oldnode)
-        return headnode
-    def check_sanity(self):
-        '''
-        Check operation timeouts and if all required parameters
-        are defined.
-        '''
-        if not self.node:  # eh?
-            common_err("%s: no xml (strange)" % self.obj_id)
-            return user_prefs.get_check_rc()
-        ra_type = self.node.getAttribute("type")
-        ra_class = self.node.getAttribute("class")
-        ra_provider = self.node.getAttribute("provider")
-        ra = RAInfo(ra_class,ra_type,ra_provider)
-        if not ra.mk_ra_node():  # no RA found?
-            ra.error("no such resource agent")
-            return user_prefs.get_check_rc()
-        params = []
-        for c in self.node.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName == "instance_attributes":
-                params += nvpairs2list(c)
-        rc1 = ra.sanity_check_params(self.obj_id, params)
-        actions = {}
-        for c in self.node.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName == "operations":
-                for c2 in c.childNodes:
-                    if is_element(c2) and c2.tagName == "op":
-                        op,pl = op2list(c2)
-                        if op:
-                            actions[op] = pl
-        rc2 = ra.sanity_check_ops(self.obj_id, actions)
-        return rc1 | rc2
-
-class CibContainer(CibObject):
-    '''
-    Groups and clones and ms.
-    '''
-    def repr_cli(self,node = None,format = True):
-        if not node:
-            node = self.node
-        l = []
-        l.append(cont_head(node))
-        cli_add_description(node,l)
-        for c in node.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName == "instance_attributes":
-                l.append("%s %s" % \
-                    (cli_display.keyword("params"), \
-                    cli_pairs(nvpairs2list(c))))
-            elif c.tagName == "meta_attributes":
-                l.append("%s %s" % \
-                    (cli_display.keyword("meta"), \
-                    cli_pairs(nvpairs2list(c))))
-        return cli_format(l,format)
-    def cli2node(self,cli,oldnode = None):
-        cli_list = mk_cli_list(cli)
-        if not cli_list:
-            return None
-        if not oldnode:
-            oldnode = self.node
-        head = copy.copy(cli_list[0])
-        head[0] = backtrans[head[0]]
-        headnode = mkxmlsimple(head,oldnode,'grp')
-        id_hint = headnode.getAttribute("id")
-        for e in cli_list[1:]:
-            n = mkxmlnode(e,oldnode,id_hint)
-            headnode.appendChild(n)
-        v = find_value(head[1],"$children")
-        if v:
-            for child_id in v:
-                obj = cib_factory.find_object(child_id)
-                if obj:
-                    n = obj.node.cloneNode(1)
-                    headnode.appendChild(n)
-                else:
-                    no_object_err(child_id)
-        remove_id_used_attributes(oldnode)
-        return headnode
-
-class CibLocation(CibObject):
-    '''
-    Location constraint.
-    '''
-    def repr_cli(self,node = None,format = True):
-        if not node:
-            node = self.node
-        l = []
-        l.append(location_head(node))
-        for c in node.childNodes:
-            if not is_element(c):
-                continue
-            if c.tagName == "rule":
-                l.append("%s %s" % \
-                    (cli_display.keyword("rule"), cli_rule(c)))
-        return cli_format(l,format)
-    def cli2node(self,cli,oldnode = None):
-        cli_list = mk_cli_list(cli)
-        if not cli_list:
-            return None
-        if not oldnode:
-            oldnode = self.node
-        head = copy.copy(cli_list[0])
-        head[0] = backtrans[head[0]]
-        headnode = mkxmlsimple(head,oldnode,'location')
-        id_hint = headnode.getAttribute("id")
-        oldrule = None
-        for e in cli_list[1:]:
-            if e[0] in ("expression","date_expression"):
-                n = mkxmlnode(e,oldrule,id_hint)
-            else:
-                n = mkxmlnode(e,oldnode,id_hint)
-            if keyword_cmp(e[0], "rule"):
-                add_missing_attr(n)
-                rule = n
-                headnode.appendChild(n)
-                oldrule = lookup_node(rule,oldnode,location_only=True)
-            else:
-                rule.appendChild(n)
-        remove_id_used_attributes(oldnode)
-        return headnode
-
-class CibSimpleConstraint(CibObject):
-    '''
-    Colocation and order constraints.
-    '''
-    def repr_cli(self,node = None,format = True):
-        if not node:
-            node = self.node
-        l = []
-        l.append(simple_constraint_head(node))
-        return cli_format(l,format)
-    def cli2node(self,cli,oldnode = None):
-        if type(cli) == type('') or type(cli) == type(u''):
-            cli_list = parse_cli(cli)
-        else:
-            cli_list = cli
-        if not cli_list:
-            return None
-        if not oldnode:
-            oldnode = self.node
-        head = copy.copy(cli_list[0])
-        head[0] = backtrans[head[0]]
-        headnode = mkxmlsimple(head,oldnode,'')
-        id_hint = headnode.getAttribute("id")
-        for e in cli_list[1:]:
-            # if more than one element, it's a resource set
-            n = mkxmlnode(e,oldnode,id_hint)
-            headnode.appendChild(n)
-        remove_id_used_attributes(oldnode)
-        return headnode
-
-class CibProperty(CibObject):
-    '''
-    Cluster properties.
-    '''
-    def repr_cli(self,node = None,format = True):
-        if not node:
-            node = self.node
-        l = []
-        l.append(cli_display.keyword(self.obj_type))
-        properties = nvpairs2list(node, add_id = True)
-        for n,v in properties:
-            if n == "$id":
-                l[0] = '%s %s="%s"' % (l[0],n,v)
-            else:
-                l.append(nvpair_format(n,v))
-        return cli_format(l,format)
-    def cli2node(self,cli,oldnode = None):
-        cli_list = mk_cli_list(cli)
-        if not cli_list:
-            return None
-        if not oldnode:
-            oldnode = cib_factory.topnode[cib_object_map[self.xml_obj_type][2]]
-        head = copy.copy(cli_list[0])
-        head[0] = backtrans[head[0]]
-        obj_id = find_value(head[1],"$id")
-        if not obj_id:
-            obj_id = cib_object_map[self.xml_obj_type][3]
-        headnode = mkxmlnode(head,oldnode,obj_id)
-        remove_id_used_attributes(oldnode)
-        return headnode
-    def matchcli(self,cli_list):
-        head = cli_list[0]
-        return self.obj_type == head[0] \
-            and self.obj_id == find_value(head[1],"$id")
-
-def get_default(property):
-    v = None
-    if cib_factory.is_cib_sane():
-        v = cib_factory.get_property(property)
-    if not v:
-        v = pe_metadata.param_default(property)
-    return v
-
-def cib_delete_element(obj):
-    'Remove one element from the CIB.'
-    if obj.xml_obj_type in defaults_tags:
-        node = cib_factory.createElement("meta_attributes")
-    else:
-        node = cib_factory.createElement(obj.xml_obj_type)
-    node.setAttribute("id",obj.obj_id)
-    rc = pipe_string("%s -D" % cib_piped, node.toxml())
-    if rc != 0:
-        update_err(obj.obj_id,'-D',node.toprettyxml())
-    node.unlink()
-    return rc
-def cib_update_elements(upd_list):
-    'Update a set of objects in the CIB.'
-    l = [x.obj_id for x in upd_list]
-    o = CibObjectSetRaw(*l)
-    xml = o.repr_configure()
-    rc = pipe_string("%s -U" % cib_piped, xml)
-    if rc != 0:
-        update_err(' '.join(l),'-U',xml)
-    return rc
-def cib_replace_element(obj):
-    rc = pipe_string("%s -R -o %s" % \
-        (cib_piped, obj.parent_type), obj.node.toxml())
-    if rc != 0:
-        update_err(obj.obj_id,'-R',obj.node.toprettyxml())
-    return rc
-def cib_delete_moved_children(obj):
-    for c in obj.children:
-        if c.origin == "cib" and c.moved:
-            cib_delete_element(c)
-
-# xml -> cli translations (and classes)
-cib_object_map = {
-    "node": ( "node", CibNode, "nodes" ),
-    "primitive": ( "primitive", CibPrimitive, "resources" ),
-    "group": ( "group", CibContainer, "resources" ),
-    "clone": ( "clone", CibContainer, "resources" ),
-    "master": ( "ms", CibContainer, "resources" ),
-    "rsc_location": ( "location", CibLocation, "constraints" ),
-    "rsc_colocation": ( "colocation", CibSimpleConstraint, "constraints" ),
-    "rsc_order": ( "order", CibSimpleConstraint, "constraints" ),
-    "cluster_property_set": ( "property", CibProperty, "crm_config", "cib-bootstrap-options" ),
-    "rsc_defaults": ( "rsc_defaults", CibProperty, "rsc_defaults", "rsc-options" ),
-    "op_defaults": ( "op_defaults", CibProperty, "op_defaults", "op-options" ),
-}
-backtrans = odict()  # generate a translation cli -> tag
-for key in cib_object_map:
-    backtrans[cib_object_map[key][0]] = key
-cib_topnodes = []  # get a list of parents
-for key in cib_object_map:
-    if not cib_object_map[key][2] in cib_topnodes:
-        cib_topnodes.append(cib_object_map[key][2])
-
-class CibFactory(object):
-    '''
-    Juggle with CIB objects.
-    See check_structure below for details on the internal cib
-    representation.
-    '''
-    def __init__(self):
-        self.init_vars()
-        self.regtest = regression_tests
-        self.all_committed = True # has commit produced error
-        self._no_constraint_rm_msg = False # internal (just not to produce silly messages)
-        self.supported_cib_re = "^pacemaker-1[.]0$"
-    def is_cib_sane(self):
-        if not self.doc:
-            empty_cib_err()
-            return False
-        return True
-    #
-    # check internal structures
-    #
-    def check_topnode(self,obj):
-        if not obj.node.parentNode.isSameNode(self.topnode[obj.parent_type]):
-            common_err("object %s is not linked to %s"%(obj.obj_id,obj.parent_type))
-    def check_parent(self,obj,parent):
-        if not obj in parent.children:
-            common_err("object %s does not reference its child %s"%(parent.obj_id,obj.obj_id))
-            return False
-        if not parent.node.isSameNode(obj.node.parentNode):
-            common_err("object %s node is not a child of its parent %s, but %s:%s"%(obj.obj_id,parent.obj_id,obj.node.tagName,obj.node.getAttribute("id")))
-            return False
-    def check_structure(self):
-        #print "Checking structure..."
-        if not self.doc:
-            empty_cib_err()
-            return False
-        rc = True
-        for obj in self.cib_objects:
-            #print "Checking %s... (%s)" % (obj.obj_id,obj.nocli)
-            if obj.parent:
-                if self.check_parent(obj,obj.parent) == False:
-                    rc = False
-            else:
-                if self.check_topnode(obj) == False:
-                    rc = False
-            for child in obj.children:
-                if self.check_parent(child,child.parent) == False:
-                    rc = False
-        return rc
-    def regression_testing(self,param):
-        # provide some help for regression testing
-        # in particular by trying to provide output which is
-        # easier to predict
-        if param == "off":
-            self.regtest = False
-        elif param == "on":
-            self.regtest = True
-        else:
-            common_warn("bad parameter for regtest: %s" % param)
-    def createElement(self,tag):
-        if self.doc:
-            return self.doc.createElement(tag)
-        else:
-            empty_cib_err()
-    def is_cib_supported(self,cib):
-        'Do we support this CIB?'
-        req = cib.getAttribute("crm_feature_set")
-        validator = cib.getAttribute("validate-with")
-        if validator and re.match(self.supported_cib_re,validator):
-            return True
-        cib_ver_unsupported_err(validator,req)
-        return False
-    def upgrade_cib_06to10(self,force = False):
-        'Upgrade the CIB from 0.6 to 1.0.'
-        if not self.doc:
-            empty_cib_err()
-            return False
-        cib = self.doc.getElementsByTagName("cib")
-        if not cib:
-            common_err("CIB has no cib element")
-            return False
-        req = cib[0].getAttribute("crm_feature_set")
-        validator = cib[0].getAttribute("validate-with")
-        if force or not validator or re.match("0[.]6",validator):
-            return ext_cmd(cib_upgrade) == 0
-    def import_cib(self):
-        'Parse the current CIB (from cibadmin -Q).'
-        self.doc,cib = read_cib(cibdump2doc)
-        if not self.doc:
-            return False
-        if not cib:
-            common_err("CIB has no cib element")
-            self.reset()
-            return False
-        if not self.is_cib_supported(cib):
-            self.reset()
-            return False
-        for attr in cib.attributes.keys():
-            self.cib_attrs[attr] = cib.getAttribute(attr)
-        for t in cib_topnodes:
-            self.topnode[t] = get_conf_elem(self.doc, t)
-            if not self.topnode[t]:
-                self.topnode[t] = mk_topnode(self.doc, t)
-                self.missing_topnodes.append(t)
-            if not self.topnode[t]:
-                common_err("could not create %s node; out of memory?" % t)
-                self.reset()
-                return False
-        return True
-    #
-    # create a doc from the list of objects
-    # (used by CibObjectSetRaw)
-    #
-    def regtest_filter(self,cib):
-        for attr in ("epoch","admin_epoch"):
-            if cib.getAttribute(attr):
-                cib.setAttribute(attr,"0")
-        for attr in ("cib-last-written",):
-            if cib.getAttribute(attr):
-                cib.removeAttribute(attr)
-    def set_cib_attributes(self,cib):
-        for attr in self.cib_attrs:
-            cib.setAttribute(attr,self.cib_attrs[attr])
-        if self.regtest:
-            self.regtest_filter(cib)
-    def objlist2doc(self,obj_list,obj_filter = None):
-        '''
-        Return document containing objects in obj_list.
-        Must remove all children from the object list, because
-        printing xml of parents will include them.
-        Optional filter to sieve objects.
-        '''
-        doc,cib,crm_config,rsc_defaults,op_defaults,nodes,resources,constraints = new_cib()
-        # get only top parents for the objects in the list
-        # e.g. if we get a primitive which is part of a clone,
-        # then the clone gets in, not the primitive
-        # dict will weed out duplicates
-        d = {}
-        for obj in obj_list:
-            if obj_filter and not obj_filter(obj):
-                continue
-            d[obj.top_parent()] = 1
-        for obj in d:
-            i_node = doc.importNode(obj.node,1)
-            if obj.parent_type == "nodes":
-                nodes.appendChild(i_node)
-            elif obj.parent_type == "resources":
-                resources.appendChild(i_node)
-            elif obj.parent_type == "constraints":
-                constraints.appendChild(i_node)
-            elif obj.parent_type == "crm_config":
-                crm_config.appendChild(i_node)
-            elif obj.parent_type == "rsc_defaults":
-                rsc_defaults.appendChild(i_node)
-            elif obj.parent_type == "op_defaults":
-                op_defaults.appendChild(i_node)
-        self.set_cib_attributes(cib)
-        return doc
-    #
-    # commit changed objects to the CIB
-    #
-    def attr_match(self,c,a):
-        'Does attribute match?'
-        try: cib_attr = self.cib_attrs[a]
-        except: cib_attr = None
-        return c.getAttribute(a) == cib_attr
-    def is_current_cib_equal(self, silent = False):
-        if self.overwrite:
-            return True
-        doc,cib = read_cib(cibdump2doc)
-        if not doc:
-            return False
-        if not cib:
-            doc.unlink()
-            return False
-        rc = self.attr_match(cib,'epoch') and \
-                self.attr_match(cib,'admin_epoch')
-        if not silent and not rc:
-            common_warn("CIB changed in the meantime: won't touch it!")
-        doc.unlink()
-        return rc
-    def add_missing_topnodes(self):
-        cib_create_topnode = "cibadmin -C -o configuration -X"
-        for tag in self.missing_topnodes:
-            if not self.topnode[tag].hasChildNodes():
-                continue
-            if ext_cmd("%s '<%s/>'" % (cib_create_topnode, tag)) != 0:
-                common_err("could not create %s in the cib" % tag)
-                return False
-        return True
-    def state_header(self):
-        'Print object status header'
-        print CibObject.state_fmt % \
-            ("","origin","updated","moved","invalid","parent","children")
-    def showobjects(self):
-        self.state_header()
-        for obj in self.cib_objects:
-            obj.dump_state()
-        if self.remove_queue:
-            print "Remove queue:"
-            for obj in self.remove_queue:
-                obj.dump_state()
-    def showqueue(self, title, obj_filter):
-        upd_list = self.cib_objs4cibadmin(obj_filter)
-        if title == "delete":
-            upd_list += self.remove_queue
-        if upd_list:
-            s = ''
-            upd_list = processing_sort_cli(upd_list)
-            if title == "delete":
-                upd_list = reversed(upd_list)
-            for obj in upd_list:
-                s = s + " " + obj.obj_string()
-            print "%s:%s" % (title,s)
-    def showqueues(self):
-        'Show what is going to happen on commit.'
-        # 1. remove objects (incl. modified constraints)
-        self.showqueue("delete", lambda o: 
-            o.origin == "cib" and (o.updated or o.recreate) and is_constraint(o.node))
-        # 2. update existing objects
-        self.showqueue("replace", lambda o: \
-            o.origin != 'user' and o.updated and not is_constraint(o.node))
-        # 3. create new objects
-        self.showqueue("create", lambda o: \
-            o.origin == 'user' and not is_constraint(o.node))
-        # 4. create objects moved from a container
-        self.showqueue("create", lambda o: \
-            not o.parent and o.moved and o.origin == "cib")
-        # 5. create constraints
-        self.showqueue("create", lambda o: is_constraint(o.node) and \
-            (((o.updated or o.recreate) and o.origin == "cib") or o.origin == "user"))
-    def commit(self):
-        'Commit the configuration to the CIB.'
-        if not self.doc:
-            empty_cib_err()
-            return False
-        if not self.add_missing_topnodes():
-            return False
-        # all_committed is updated in the invoked object methods
-        self.all_committed = True
-        cnt = 0
-        # 1. remove objects (incl. modified constraints)
-        cnt += self.delete_objects(lambda o: 
-            o.origin == "cib" and (o.updated or o.recreate) and is_constraint(o.node))
-        # 2. update existing objects
-        cnt += self.replace_objects(lambda o: \
-            o.origin != 'user' and o.updated and not is_constraint(o.node))
-        # 3. create new objects
-        cnt += self.create_objects(lambda o: \
-            o.origin == 'user' and not is_constraint(o.node))
-        # 4. create objects moved from a container
-        cnt += self.create_objects(lambda o: \
-            not o.parent and o.moved and o.origin == "cib")
-        # 5. create constraints
-        cnt += self.create_objects(lambda o: is_constraint(o.node) and \
-            (((o.updated or o.recreate) and o.origin == "cib") or o.origin == "user"))
-        if cnt:
-            # reload the cib!
-            self.reset()
-            self.initialize()
-        return self.all_committed
-    def cib_objs4cibadmin(self,obj_filter):
-        '''
-        Filter objects from our cib_objects list. But add only
-        top parents.
-        For this to work, the filter must not filter out parents.
-        That's guaranteed by the updated flag propagation.
-        '''
-        upd_list = []
-        for obj in self.cib_objects:
-            if not obj_filter or obj_filter(obj):
-                if not obj.parent and not obj in upd_list:
-                    upd_list.append(obj)
-        return upd_list
-    def delete_objects(self,obj_filter):
-        cnt = 0
-        upd_list = self.cib_objs4cibadmin(obj_filter)
-        if not (self.remove_queue + upd_list):
-            return 0
-        obj_list = processing_sort_cli(self.remove_queue + upd_list)
-        for obj in reversed(obj_list):
-            if cib_delete_element(obj) == 0:
-                if obj in self.remove_queue:
-                    self.remove_queue.remove(obj)
-                cnt += 1
-            else:
-                self.all_committed = False
-        return cnt
-    def create_objects(self,obj_filter):
-        upd_list = self.cib_objs4cibadmin(obj_filter)
-        if not upd_list:
-            return 0
-        for obj in upd_list:
-            cib_delete_moved_children(obj)
-        if cib_update_elements(upd_list) == 0:
-            for obj in upd_list:
-                obj.reset_updated()
-            return len(upd_list)
-        else:
-            self.all_committed = False
-            return 0
-    def replace_objects(self,obj_filter):
-        cnt = 0
-        upd_list = self.cib_objs4cibadmin(obj_filter)
-        if not upd_list:
-            return 0
-        for obj in processing_sort_cli(upd_list):
-            #print obj.node.toprettyxml()
-            cib_delete_moved_children(obj)
-            if cib_replace_element(obj) == 0:
-                cnt += 1
-                obj.reset_updated()
-            else:
-                self.all_committed = False
-        return cnt
-    #
-    # initialize cib_objects from CIB
-    #
-    def save_node(self,node,pnode = None):
-        if not pnode:
-            pnode = node
-        obj = cib_object_map[pnode.tagName][1](pnode.tagName)
-        obj.origin = "cib"
-        self.cib_objects.append(obj)
-        if not obj.save_xml(node):
-            obj.nocli = True
-    def populate(self):
-        "Walk the cib and collect cib objects."
-        all_nodes = get_interesting_nodes(self.doc,[])
-        if not all_nodes:
-            return
-        for node in processing_sort(all_nodes):
-            if is_defaults(node):
-                for c in node.childNodes:
-                    if not is_element(c) or c.tagName != "meta_attributes":
-                        continue
-                    self.save_node(c,node)
-            else:
-                self.save_node(node)
-        for obj in self.cib_objects:
-            obj.update_links()
-    def initialize(self):
-        if self.doc:
-            return True
-        if not self.import_cib():
-            return False
-        sanitize_cib(self.doc)
-        show_unrecognized_elems(self.doc)
-        self.populate()
-        return self.check_structure()
-    def init_vars(self):
-        self.doc = None  # the cib
-        self.topnode = {}
-        for t in cib_topnodes:
-            self.topnode[t] = None
-        self.missing_topnodes = []
-        self.cib_attrs = {} # cib version dictionary
-        self.cib_objects = [] # a list of cib objects
-        self.remove_queue = [] # a list of cib objects to be removed
-        self.overwrite = False # update cib unconditionally
-    def reset(self):
-        if not self.doc:
-            return
-        self.doc.unlink()
-        self.init_vars()
-        id_store.clear()
-    def find_object(self,obj_id):
-        "Find an object for id."
-        for obj in self.cib_objects:
-            if obj.obj_id == obj_id:
-                return obj
-        return None
-    #
-    # tab completion functions
-    #
-    def id_list(self):
-        "List of ids (for completion)."
-        return [x.obj_id for x in self.cib_objects]
-    def prim_id_list(self):
-        "List of primitives ids (for group completion)."
-        return [x.obj_id for x in self.cib_objects if x.obj_type == "primitive"]
-    def children_id_list(self):
-        "List of child ids (for clone/master completion)."
-        return [x.obj_id for x in self.cib_objects if x.obj_type in children_tags]
-    def rsc_id_list(self):
-        "List of resource ids (for constraint completion)."
-        return [x.obj_id for x in self.cib_objects \
-            if x.obj_type in resource_tags and not x.parent]
-    def f_prim_id_list(self):
-        "List of possible primitives ids (for group completion)."
-        return [x.obj_id for x in self.cib_objects \
-            if x.obj_type == "primitive" and not x.parent]
-    def f_children_id_list(self):
-        "List of possible child ids (for clone/master completion)."
-        return [x.obj_id for x in self.cib_objects \
-            if x.obj_type in children_tags and not x.parent]
-    #
-    # a few helper functions
-    #
-    def find_object_for_node(self,node):
-        "Find an object which matches a dom node."
-        for obj in self.cib_objects:
-            if node.getAttribute("id") == obj.obj_id:
-                return obj
-        return None
-    def resolve_id_ref(self,attr_list_type,id_ref):
-        '''
-        User is allowed to specify id_ref either as a an object
-        id or as attributes id. Here we try to figure out which
-        one, i.e. if the former is the case to find the right
-        id to reference.
-        '''
-        obj= self.find_object(id_ref)
-        if obj:
-            node_l = obj.node.getElementsByTagName(attr_list_type)
-            if node_l:
-                if len(node_l) > 1:
-                    common_warn("%s contains more than one %s, using first" % \
-                        (obj.obj_id,attr_list_type))
-                id = node_l[0].getAttribute("id")
-                if not id:
-                    common_err("%s reference not found" % id_ref)
-                    return id_ref # hope that user will fix that
-                return id
-        # verify if id_ref exists
-        node_l = self.doc.getElementsByTagName(attr_list_type)
-        for node in node_l:
-            if node.getAttribute("id") == id_ref:
-                return id_ref
-        common_err("%s reference not found" % id_ref)
-        return id_ref # hope that user will fix that
-    def get_property(self,property):
-        '''
-        Get the value of the given cluster property.
-        '''
-        for obj in self.cib_objects:
-            if obj.obj_type == "property" and obj.node:
-                pl = nvpairs2list(obj.node)
-                v = find_value(pl, property)
-                if v:
-                    return v
-        return None
-    def new_object(self,obj_type,obj_id):
-        "Create a new object of type obj_type."
-        if id_in_use(obj_id):
-            return None
-        for xml_obj_type,v in cib_object_map.items():
-            if v[0] == obj_type:
-                obj = v[1](xml_obj_type,obj_id)
-                if obj.obj_id:
-                    return obj
-                else:
-                    return None
-        return None
-    def mkobj_list(self,mode,*args):
-        obj_list = []
-        for obj in self.cib_objects:
-            f = lambda: obj.filter(*args)
-            if not f():
-                continue
-            if mode == "cli" and obj.nocli:
-                obj_cli_err(obj.obj_id)
-                continue
-            obj_list.append(obj)
-        return obj_list
-    def has_cib_changed(self):
-        return self.mkobj_list("xml","changed") or self.remove_queue
-    def verify_constraints(self,cli_list):
-        '''
-        Check if all resources referenced in a constraint exist
-        '''
-        rc = True
-        head = cli_list[0]
-        constraint_id = find_value(head[1],"id")
-        for obj_id in referenced_resources_cli(cli_list):
-            if not self.find_object(obj_id):
-                constraint_norefobj_err(constraint_id,obj_id)
-                rc = False
-        return rc
-    def verify_children(self,cli_list):
-        '''
-        Check prerequisites:
-          a) all children must exist
-          b) no child may have other parent than me
-          (or should we steal children?)
-          c) there may not be duplicate children
-        '''
-        head = cli_list[0]
-        obj_type = head[0]
-        obj_id = find_value(head[1],"id")
-        c_ids = find_value(head[1],"$children")
-        if not c_ids:
-            return True
-        rc = True
-        c_dict = {}
-        for child_id in c_ids:
-            if not self.verify_child(child_id,obj_type,obj_id):
-                rc = False
-            if child_id in c_dict:
-                common_err("in group %s child %s listed more than once"%(obj_id,child_id))
-                rc = False
-            c_dict[child_id] = 1
-        return rc
-    def verify_child(self,child_id,obj_type,obj_id):
-        'Check if child exists and obj_id is (or may become) its parent.'
-        child = self.find_object(child_id)
-        if not child:
-            no_object_err(child_id)
-            return False
-        if child.parent and child.parent.obj_id != obj_id:
-            common_err("%s already in use at %s"%(child_id,child.parent.obj_id))
-            return False
-        if obj_type == "group" and child.obj_type != "primitive":
-            common_err("a group may contain only primitives; %s is %s"%(child_id,child.obj_type))
-            return False
-        if not child.obj_type in children_tags:
-            common_err("%s may contain a primitive or a group; %s is %s"%(obj_type,child_id,child.obj_type))
-            return False
-        return True
-    def verify_cli(self,cli_list):
-        '''
-        Can we create this object given its CLI representation.
-        This is not about syntax, we're past that, but about
-        semantics.
-        Right now we check if the children, if any, are fit for
-        the parent. And if this is a constraint, if all
-        referenced resources are present.
-        '''
-        rc = True
-        if not self.verify_children(cli_list):
-            rc = False
-        if not self.verify_constraints(cli_list):
-            rc = False
-        return rc
-    def create_object(self,*args):
-        s = []
-        s += args
-        return self.create_from_cli(parse_cli(s)) != None
-    def set_property_cli(self,cli_list):
-        head_pl = cli_list[0]
-        obj_type = head_pl[0].lower()
-        pset_id = find_value(head_pl[1],"$id")
-        if pset_id:
-            head_pl[1].remove(["$id",pset_id])
-        else:
-            pset_id = cib_object_map[backtrans[obj_type]][3]
-        obj = self.find_object(pset_id)
-        if not obj:
-            if not is_id_valid(pset_id):
-                invalid_id_err(pset_id)
-                return None
-            obj = self.new_object(obj_type,pset_id)
-            if not obj:
-                return None
-            self.topnode[obj.parent_type].appendChild(obj.node)
-            obj.origin = "user"
-            self.cib_objects.append(obj)
-        for n,v in head_pl[1]:
-            set_nvpair(obj.node,n,v)
-        obj.updated = True
-        return obj
-    def add_op(self,cli_list):
-        '''Add an op to a primitive.'''
-        head = cli_list[0]
-        # does the referenced primitive exist
-        rsc_id = find_value(head[1],"rsc")
-        rsc_obj = cib_factory.find_object(rsc_id)
-        if not rsc_obj:
-            no_object_err(rsc_id)
-            return None
-        if rsc_obj.obj_type != "primitive":
-            common_err("%s is not a primitive" % rsc_id)
-            return None
-        # check if there is already an op with the same interval
-        name = find_value(head[1], "name")
-        interval = find_value(head[1], "interval")
-        if find_operation(rsc_obj.node,name,interval):
-            common_err("%s already has a %s op with interval %s" % \
-                (rsc_id, name, interval))
-            return None
-        # drop the rsc attribute
-        head[1].remove(["rsc",rsc_id])
-        # create an xml node
-        mon_node = mkxmlsimple(head, None, rsc_id)
-        # get the place to append it to
-        try:
-            op_node = rsc_obj.node.getElementsByTagName("operations")[0]
-        except:
-            op_node = self.createElement("operations")
-            rsc_obj.node.appendChild(op_node)
-        op_node.appendChild(mon_node)
-        # the resource is updated
-        rsc_obj.updated = True
-        rsc_obj.propagate_updated()
-        return rsc_obj
-    def create_from_cli(self,cli):
-        'Create a new cib object from the cli representation.'
-        cli_list = mk_cli_list(cli)
-        if not cli_list:
-            return None
-        if not self.verify_cli(cli_list):
-            return None
-        head = cli_list[0]
-        obj_type = head[0].lower()
-        if obj_type in nvset_cli_names:
-            return self.set_property_cli(cli_list)
-        if obj_type == "op":
-            return self.add_op(cli_list)
-        obj_id = find_value(head[1],"id")
-        if not is_id_valid(obj_id):
-            invalid_id_err(obj_id)
-            return None
-        obj = self.new_object(obj_type,obj_id)
-        if not obj:
-            return None
-        obj.node = obj.cli2node(cli_list)
-        if user_prefs.is_check_always() \
-                and obj.check_sanity() > 1:
-            id_store.remove_xml(obj.node)
-            obj.node.unlink()
-            return None
-        self.topnode[obj.parent_type].appendChild(obj.node)
-        self.adjust_children(obj,cli_list)
-        obj.origin = "user"
-        for child in obj.children:
-            # redirect constraints to the new parent
-            for c_obj in self.related_constraints(child):
-                self.remove_queue.append(c_obj.mkcopy())
-                rename_rscref(c_obj,child.obj_id,obj.obj_id)
-        # drop useless constraints which may have been created above
-        for c_obj in self.related_constraints(obj):
-            if silly_constraint(c_obj.node,obj.obj_id):
-                self._no_constraint_rm_msg = True
-                self._remove_obj(c_obj)
-                self._no_constraint_rm_msg = False
-        self.cib_objects.append(obj)
-        return obj
-    def update_moved(self,obj):
-        'Updated the moved flag. Mark affected constraints.'
-        obj.moved = not obj.moved
-        if obj.moved:
-            for c_obj in self.related_constraints(obj):
-                c_obj.recreate = True
-    def adjust_children(self,obj,cli_list):
-        '''
-        All stuff children related: manage the nodes of children,
-        update the list of children for the parent, update
-        parents in the children.
-        '''
-        head = cli_list[0]
-        children_ids = find_value(head[1],"$children")
-        if not children_ids:
-            return
-        new_children = []
-        for child_id in children_ids:
-            new_children.append(self.find_object(child_id))
-        self._relink_orphans(obj,new_children)
-        obj.children = new_children
-        self._update_children(obj)
-    def _relink_child(self,obj):
-        'Relink a child to the top node.'
-        obj.node.parentNode.removeChild(obj.node)
-        self.topnode[obj.parent_type].appendChild(obj.node)
-        self.update_moved(obj)
-        obj.parent = None
-    def _update_children(self,obj):
-        '''For composite objects: update all children nodes.
-        '''
-        # unlink all and find them in the new node
-        for child in obj.children:
-            oldnode = child.node
-            child.node = obj.find_child_in_node(child)
-            if child.children: # and children of children
-                self._update_children(child)
-            rmnode(oldnode)
-            if not child.parent:
-                self.update_moved(child)
-            if child.parent and child.parent != obj:
-                child.parent.updated = True # the other parent updated
-            child.parent = obj
-    def _relink_orphans(self,obj,new_children):
-        "New orphans move to the top level for the object type."
-        for child in obj.children:
-            if child not in new_children:
-                self._relink_child(child)
-    def add_obj(self,obj_type,node):
-        obj = self.new_object(obj_type, node.getAttribute("id"))
-        if not obj:
-            return None
-        if not obj.save_xml(node):
-            obj.nocli = True
-        obj.update_links()
-        obj.origin = "user"
-        self.cib_objects.append(obj)
-        return obj
-    def create_from_node(self,node):
-        'Create a new cib object from a document node.'
-        if not node:
-            return None
-        obj_type = cib_object_map[node.tagName][0]
-        node = self.doc.importNode(node,1)
-        obj = None
-        if is_defaults(node):
-            for c in node.childNodes:
-                if not is_element(c) or c.tagName != "meta_attributes":
-                    continue
-                obj = self.add_obj(obj_type,c)
-        else:
-            obj = self.add_obj(obj_type,node)
-        if obj:
-            self.topnode[obj.parent_type].appendChild(node)
-        return obj
-    def cib_objects_string(self, obj_list = None):
-        l = []
-        if not obj_list:
-            obj_list = self.cib_objects
-        for obj in obj_list:
-            l.append(obj.obj_string())
-        return ' '.join(l)
-    def _remove_obj(self,obj):
-        "Remove a cib object and its children."
-        # remove children first
-        # can't remove them here from obj.children!
-        common_debug("remove object %s" % obj.obj_string())
-        for child in obj.children:
-            #self._remove_obj(child)
-            # just relink, don't remove children
-            self._relink_child(child)
-        if obj.parent: # remove obj from its parent, if any
-            obj.parent.children.remove(obj)
-        id_store.remove_xml(obj.node)
-        rmnode(obj.node)
-        obj.invalid = True
-        self.add_to_remove_queue(obj)
-        self.cib_objects.remove(obj)
-        for c_obj in self.related_constraints(obj):
-            if is_simpleconstraint(c_obj.node) and obj.children:
-                # the first child inherits constraints
-                rename_rscref(c_obj,obj.obj_id,obj.children[0].obj_id)
-            delete_rscref(c_obj,obj.obj_id)
-            if silly_constraint(c_obj.node,obj.obj_id):
-                # remove invalid constraints
-                self._remove_obj(c_obj)
-                if not self._no_constraint_rm_msg:
-                    err_buf.info("hanging %s deleted" % c_obj.obj_string())
-    def related_constraints(self,obj):
-        if not is_resource(obj.node):
-            return []
-        c_list = []
-        for obj2 in self.cib_objects:
-            if not is_constraint(obj2.node):
-                continue
-            if rsc_constraint(obj.obj_id,obj2.node):
-                c_list.append(obj2)
-        return c_list
-    def add_to_remove_queue(self,obj):
-        if obj.origin == "cib":
-            self.remove_queue.append(obj)
-        #print self.cib_objects_string(self.remove_queue)
-    def delete_1(self,obj):
-        '''
-        Remove an object and its parent in case the object is the
-        only child.
-        '''
-        if obj.parent and len(obj.parent.children) == 1:
-            self.delete_1(obj.parent)
-        if obj in self.cib_objects: # don't remove parents twice
-            self._remove_obj(obj)
-    def delete(self,*args):
-        'Delete a cib object.'
-        if not self.doc:
-            empty_cib_err()
-            return False
-        rc = True
-        l = []
-        for obj_id in args:
-            obj = self.find_object(obj_id)
-            if not obj:
-                no_object_err(obj_id)
-                rc = False
-                continue
-            if is_rsc_running(obj_id):
-                common_warn("resource %s is running, can't delete it" % obj_id)
-            else:
-                l.append(obj)
-        if l:
-            l = processing_sort_cli(l)
-            for obj in reversed(l):
-                self.delete_1(obj)
-        return rc
-    def remove_on_rename(self,obj):
-        '''
-        If the renamed object is coming from the cib, then it
-        must be removed and a new one created.
-        '''
-        if obj.origin == "cib":
-            self.remove_queue.append(obj.mkcopy())
-            obj.origin = "user"
-    def rename(self,old_id,new_id):
-        '''
-        Rename a cib object.
-        - check if the resource (if it's a resource) is stopped
-        - check if the new id is not taken
-        - find the object with old id
-        - rename old id to new id in all related objects
-          (constraints)
-        - if the object came from the CIB, then it must be
-          deleted and the one with the new name created
-        - rename old id to new id in the object
-        '''
-        if not self.doc:
-            empty_cib_err()
-            return False
-        if id_in_use(new_id):
-            return False
-        obj = self.find_object(old_id)
-        if not obj:
-            no_object_err(old_id)
-            return False
-        if not obj.can_be_renamed():
-            return False
-        for c_obj in self.related_constraints(obj):
-            rename_rscref(c_obj,old_id,new_id)
-        self.remove_on_rename(obj)
-        rename_id(obj.node,old_id,new_id)
-        obj.obj_id = new_id
-        id_store.rename(old_id,new_id)
-        obj.updated = True
-        obj.propagate_updated()
-    def erase(self):
-        "Remove all cib objects."
-        # remove only bottom objects and no constraints
-        # the rest will automatically follow
-        if not self.doc:
-            empty_cib_err()
-            return False
-        erase_ok = True
-        l = []
-        for obj in [obj for obj in self.cib_objects \
-                if not obj.children and not is_constraint(obj.node) \
-                and obj.obj_type != "node" ]:
-            if is_rsc_running(obj.obj_id):
-                common_warn("resource %s is running, can't delete it" % obj.obj_id)
-                erase_ok = False
-            else:
-                l.append(obj)
-        if not erase_ok:
-            common_err("CIB erase aborted (nothing was deleted)")
-            return False
-        self._no_constraint_rm_msg = True
-        for obj in l:
-            self.delete(obj.obj_id)
-        self._no_constraint_rm_msg = False
-        remaining = 0
-        for obj in self.cib_objects:
-            if obj.obj_type != "node":
-                remaining += 1
-        if remaining > 0:
-            common_err("strange, but these objects remained:")
-            for obj in self.cib_objects:
-                if obj.obj_type != "node":
-                    print >> sys.stderr, obj.obj_string()
-            self.cib_objects = []
-        return True
-    def erase_nodes(self):
-        "Remove nodes only."
-        if not self.doc:
-            empty_cib_err()
-            return False
-        l = [obj for obj in self.cib_objects if obj.obj_type == "node"]
-        for obj in l:
-            self.delete(obj.obj_id)
-    def refresh(self):
-        "Refresh from the CIB."
-        self.reset()
-        self.initialize()
-
-class TopLevel(UserInterface):
-    '''
-    The top level.
-    '''
-    crm_mon = "crm_mon -1"
-    status_opts = {
-        "bynode": "-n",
-        "inactive": "-r",
-        "ops": "-o",
-        "timing": "-t",
-        "failcounts": "-f",
-    }
-    help_table = odict()
-    help_table["."] = ("","""This is the CRM command line interface program.""")
-    help_table["cib"] = ("manage shadow CIBs", """
-A shadow CIB is a regular cluster configuration which is kept in
-a file. The CRM and the CRM tools may manage a shadow CIB in the
-same way as the live CIB (i.e. the current cluster configuration).
-A shadow CIB may be applied to the cluster in one step.
-""")
-    help_table["resource"] = ("resources management", """
-Everything related to resources management is available at this
-level. Most commands are implemented using the crm_resource(8)
-program.
-""")
-    help_table["node"] = ("nodes management", """
-A few node related tasks such as node standby are implemented
-here.
-""")
-    help_table["options"] = ("user preferences", """
-Several user preferences are available. Note that it is possible
-to save the preferences to a startup file.
-""")
-    help_table["configure"] = ("CRM cluster configuration", """
-The configuration level.
-
-Note that you can change the working CIB at the cib level. It is
-advisable to configure shadow CIBs and then commit them to the
-cluster.
-""")
-    help_table["ra"] = ("resource agents information center", """
-This level contains commands which show various information about
-the installed resource agents. It is available both at the top
-level and at the `configure` level.
-""")
-    help_table["status"] = ("show cluster status", """
-Show cluster status. The status is displayed by crm_mon. Supply
-additional arguments for more information or different format.
-See crm_mon(8) for more details.
-
-Usage:
-...............
-        status [<option> ...]
-
-        option :: bynode | inactive | ops | timing | failcounts
-...............
-""")
-    help_table["quit"] = ("exit the program", "")
-    help_table["help"] = ("show help", "")
-    help_table["end"] = ("go back one level", "")
-    def __init__(self):
-        UserInterface.__init__(self)
-        self.cmd_table['cib'] = CibShadow
-        self.cmd_table['resource'] = RscMgmt
-        self.cmd_table['configure'] = CibConfig
-        self.cmd_table['node'] = NodeMgmt
-        self.cmd_table['options'] = CliOptions
-        self.cmd_table['status'] = (self.status,(0,5),0)
-        self.cmd_table['ra'] = RA
-        setup_aliases(self)
-    def status(self,cmd,*args):
-        """usage: status [<option> ...]
-            option :: bynode | inactive | ops | timing | failcounts
-        """
-        status_cmd = self.crm_mon
-        for par in args:
-            if par in self.status_opts:
-                status_cmd = "%s %s" % (status_cmd, self.status_opts[par])
-            else:
-                syntax_err((cmd,par), context = 'status')
-                return False
-        return ext_cmd(status_cmd) == 0
-
-class CompletionHelp(object):
-    '''
-    Print some help on whatever last word in the line.
-    '''
-    timeout = 60  # don't print again and again
-    def __init__(self):
-        self.laststamp = 0
-        self.lastitem = ''
-    def help(self,f,*args):
-        words = readline.get_line_buffer().split()
-        if not words:
-            return
-        key = words[-1]
-        if key.endswith('='):
-            key = key[0:-1]
-        if self.lastitem == key and \
-                time.time() - self.laststamp < self.timeout:
-            return
-        help_s = f(key,*args)
-        if help_s:
-            print "\n%s" % help_s
-            print "%s%s" % (prompt,readline.get_line_buffer()),
-            self.laststamp = time.time()
-            self.lastitem = key
-
-def attr_cmds(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return ["delete","set","show"]
-def listnodes():
-    if wcache.is_cached("listnodes"):
-        return wcache.retrieve("listnodes")
-    nodes = []
-    doc = cibdump2doc("nodes")
-    if not doc:
-        return []
-    nodes_node = get_conf_elem(doc, "nodes")
-    if not nodes_node:
-        return []
-    for c in nodes_node.childNodes:
-        if not is_element(c):
-            continue
-        if c.tagName != "node":
-            continue
-        if c.getAttribute("type") == 'normal':
-            nodes.append(c.getAttribute("uname"))
-    return wcache.store("property_list",nodes)
-def nodes_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return listnodes()
-def shadows_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return listshadows()
-def templates_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return listtemplates()
-def config_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return listconfigs()
-def config_list_method(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return listconfigs() + ["replace","update"]
-def shadows_live_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return listshadows() + ['live']
-def rsc_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    doc = resources_xml()
-    if not doc:
-        return []
-    nodes = get_interesting_nodes(doc,[])
-    return [x.getAttribute("id") for x in nodes if is_resource(x)]
-def null_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return []
-def loop(idx,delimiter = False):
-    "just a marker in a list"
-    pass
-def id_xml_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return cib_factory.id_list() + ['xml','changed']
-def id_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return cib_factory.id_list()
-def f_prim_id_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return cib_factory.f_prim_id_list()
-def f_children_id_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return cib_factory.f_children_id_list()
-def rsc_id_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return cib_factory.rsc_id_list()
-def status_node_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return cib_status.status_node_list()
-def status_rsc_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return cib_status.status_rsc_list()
-def node_states_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return StatusMgmt.node_states
-def ra_operations_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return StatusMgmt.ra_operations
-def lrm_exit_codes_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return StatusMgmt.lrm_exit_codes.keys()
-def lrm_status_codes_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return StatusMgmt.lrm_status_codes.keys()
-def skills_list(idx,delimiter = False):
-    if delimiter:
-        return ' '
-    return user_prefs.skill_levels.keys()
-def ra_classes_list(idx,delimiter = False):
-    if delimiter:
-        return ':'
-    return ra_classes()
-def get_primitive_type(words):
-    try:
-        idx = words.index("primitive") + 2
-        type_word = words[idx]
-    except: type_word = ''
-    return type_word
-def ra_type_list(toks,idx,delimiter):
-    if idx == 2:
-        if toks[0] == "ocf":
-            dchar = ':'
-            l = ra_providers_all()
-        else:
-            dchar = ' '
-            l = ra_types(toks[0])
-    elif idx == 3:
-        dchar = ' '
-        if toks[0] == "ocf":
-            l = ra_types(toks[0],toks[1])
-        else:
-            l = ra_types(toks[0])
-    if delimiter:
-        return dchar
-    return l
-def prim_meta_attr_list():
-    return [\
-        "allow-migrate", \
-        "globally-unique", \
-        "is-managed", \
-        "migration-threshold", \
-        "priority", \
-        "multiple-active", \
-        "failure-timeout", \
-        "resource-stickiness", \
-        "target-role", \
-        ]
-def op_attr_list():
-    return op_attributes
-def operations_list():
-    return op_cli_names
-def prim_complete_meta(ra,delimiter):
-    if delimiter:
-        return '='
-    return prim_meta_attr_list()
-def prim_complete_op(ra,delimiter):
-    words = split_buffer()
-    if (readline.get_line_buffer()[-1] == ' ' and words[-1] == "op") \
-            or (readline.get_line_buffer()[-1] != ' ' and words[-2] == "op"):
-        dchar = ' '
-        l = operations_list()
-    else:
-        if readline.get_line_buffer()[-1] == '=':
-            dchar = ' '
-            l = []
-        else:
-            dchar = '='
-            l = op_attr_list()
-    if delimiter:
-        return dchar
-    return l
-def prim_complete_params(ra,delimiter):
-    if readline.get_line_buffer()[-1] == '=':
-        dchar = ' '
-        l = []
-    else:
-        dchar = '='
-        l = ra.params().keys()
-    if delimiter:
-        return dchar
-    return l
-def prim_params_info(key,ra):
-    return ra.meta_parameter(key)
-def meta_attr_info(key,ra):
-    pass
-def op_attr_info(key,ra):
-    pass
-def get_lastkeyw(words,keyw):
-    revwords = copy.copy(words)
-    revwords.reverse()
-    for w in revwords:
-        if w in keyw:
-            return w
-def primitive_complete_complex(idx,delimiter = False):
-    '''
-    This completer depends on the content of the line, i.e. on
-    previous tokens, in particular on the type of the RA.
-    '''
-    completers_set = {
-        "params": (prim_complete_params, prim_params_info),
-        "meta": (prim_complete_meta, meta_attr_info),
-        "op": (prim_complete_op, op_attr_info),
-    }
-    # manage the resource type
-    words = readline.get_line_buffer().split()
-    type_word = get_primitive_type(words)
-    toks = type_word.split(':')
-    if toks[0] != "ocf":
-        idx += 1
-    if idx in (2,3):
-        return ra_type_list(toks,idx,delimiter)
-    # create an ra object
-    ra = None
-    ra_class,provider,rsc_type = disambiguate_ra_type(type_word)
-    if ra_type_validate(type_word,ra_class,provider,rsc_type):
-        ra = RAInfo(ra_class,rsc_type,provider)
-    keywords = completers_set.keys()
-    if idx == 4:
-        if delimiter:
-            return ' '
-        return keywords
-    lastkeyw = get_lastkeyw(words,keywords)
-    if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ':
-        if not delimiter and lastkeyw and \
-                readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1:
-            compl_help.help(completers_set[lastkeyw][1],ra)
-        if delimiter:
-            return ' '
-        return ['*']
-    else:
-        if lastkeyw:
-            return completers_set[lastkeyw][0](ra,delimiter)
-def property_complete(idx,delimiter = False):
-    '''
-    This completer depends on the content of the line, i.e. on
-    previous tokens, in particular on the type of the RA.
-    '''
-    words = readline.get_line_buffer().split()
-    if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ':
-        if not delimiter and \
-                readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1:
-            compl_help.help(prim_params_info,pe_metadata)
-        if delimiter:
-            return ' '
-        return ['*']
-    else:
-        return prim_complete_params(pe_metadata,delimiter)
-
-def topics_dict(help_tab):
-    if not help_tab:
-        return {}
-    topics = {}
-    for topic in help_tab:
-        if topic != '.':
-            topics[topic] = None
-    return topics
-
-def mk_completion_tab(obj,ctab):
-    cmd_table = obj.cmd_table
-    for key,value in cmd_table.items():
-        if key.startswith("_"):
-            continue
-        if type(value) == type(object):
-            ctab[key] = {}
-        elif key == "help":
-            ctab[key] = topics_dict(obj.help_table)
-        else:
-            try:
-                ctab[key] = value[3]
-            except:
-                ctab[key] = None
-                pass
-def lookup_dynamic(fun_list,idx,f_idx,words):
-    if not fun_list:
-        return []
-    if fun_list[f_idx] == loop:
-        f_idx -= 1
-    f = fun_list[f_idx]
-    w = words[0]
-    wordlist = f(idx)
-    delimiter = f(idx,1)
-    if len(wordlist) == 1 and wordlist[0] == '*':
-        return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:])
-    elif len(words) == 1:
-        return [x+delimiter for x in wordlist if x.startswith(w)]
-    return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:])
-def lookup_words(ctab,words):
-    if not ctab:
-        return []
-    if type(ctab) == type(()):
-        return lookup_dynamic(ctab,0,0,words)
-    if len(words) == 1:
-        return [x+' ' for x in ctab if x.startswith(words[0])]
-    elif words[0] in ctab.keys():
-        return lookup_words(ctab[words[0]],words[1:])
-    return []
-def split_buffer():
-    p = readline.get_line_buffer()
-    p = p.replace(':',' ').replace('=',' ')
-    return p.split()
-def completer(txt,state):
-    words = split_buffer()
-    if readline.get_begidx() == readline.get_endidx():
-        words.append('')
-    matched = lookup_words(levels.completion_tab,words)
-    matched.append(None)
-    return matched[state]
-
-termctrl = TerminalController()
-wcache = WCache()
-user_prefs = UserPrefs()
-id_store = IdMgmt()
-cib_factory = CibFactory()
-cib_status = CibStatus()
-cli_display = CliDisplay()
-pe_metadata = RAInfo("pengine","metadata")
-stonithd_metadata = RAInfo("stonithd","metadata")
-ra_if = RaLrmd()
-if not ra_if.good:
-    ra_if = RaOS()
-tmpfiles = []
-
-def load_rc(rcfile):
-    try: f = open(rcfile)
-    except: return
-    save_stdin = sys.stdin
-    sys.stdin = f
-    while True:
-        inp = multi_input()
-        if inp == None:
-            break
-        try: parse_line(levels,shlex.split(inp))
-        except ValueError, msg:
-            common_err(msg)
-    f.close()
-    sys.stdin = save_stdin
-
-def multi_input(prompt = ''):
-    """
-    Get input from user
-    Allow multiple lines using a continuation character
-    """
-    global lineno
-    line = []
-    while True:
-        try:
-            text = raw_input(prompt)
-        except EOFError:
-            return None
-        if lineno >= 0:
-            lineno += 1
-        if regression_tests:
-            print ".INP:",text
-            sys.stdout.flush()
-            sys.stderr.flush()
-        stripped = text.strip()
-        if stripped.endswith('\\'):
-            stripped = stripped.rstrip('\\')
-            line.append(stripped)
-            if prompt:
-                prompt = '> '
-        else:
-            line.append(stripped)
-            break
-    return ''.join(line)
-
-class Levels(object):
-    '''
-    Keep track of levels and prompts.
-    '''
-    def __init__(self,start_level):
-        self._marker = 0
-        self._in_transit = False
-        self.level_stack = []
-        self.comp_stack = []
-        self.current_level = start_level()
-        self.parse_root = self.current_level.cmd_table
-        self.prompts = []
-        self.completion_tab = {}
-        mk_completion_tab(self.current_level,self.completion_tab)
-    def getprompt(self):
-        return ' '.join(self.prompts)
-    def mark(self):
-        self._marker = len(self.level_stack)
-        self._in_transit = False
-    def release(self):
-        while len(self.level_stack) > self._marker:
-            self.droplevel()
-    def new_level(self,level_obj,token):
-        self.level_stack.append(self.current_level)
-        self.comp_stack.append(self.completion_tab)
-        self.prompts.append(token)
-        self.current_level = level_obj()
-        self.parse_root = self.current_level.cmd_table
-        try:
-            if not self.completion_tab[token]:
-                mk_completion_tab(self.current_level,self.completion_tab[token])
-            self.completion_tab = self.completion_tab[token]
-        except:
-            pass
-        self._in_transit = True
-    def previous(self):
-        if self.level_stack:
-            return self.level_stack[-1]
-    def droplevel(self):
-        if self.level_stack:
-            self.current_level.end_game(self._in_transit)
-            self.current_level = self.level_stack.pop()
-            self.completion_tab = self.comp_stack.pop()
-            self.parse_root = self.current_level.cmd_table
-            self.prompts.pop()
-
-def check_args(args,argsdim):
-    if not argsdim: return True
-    if len(argsdim) == 1:
-        minargs = argsdim[0]
-        return len(args) >= minargs
-    else:
-        minargs,maxargs = argsdim
-        return len(args) >= minargs and len(args) <= maxargs
-
-#
-# Note on parsing
-#
-# Parsing tables are python dictionaries.
-#
-# Keywords are used as keys and the corresponding values are
-# lists (actually tuples, since they should be read-only) or
-# classes. In the former case, the keyword is a terminal and
-# in the latter, a new object for the class is created. The class
-# must have the cmd_table variable.
-#
-# The list has the following content:
-#
-# function: a function to handle this command
-# numargs_list: number of minimum/maximum arguments; for example,
-#   (0,1) means one optional argument, (1,1) one required; if the
-#   list is empty then the function will parse arguments itself
-# required minimum skill level: operator, administrator, expert
-#   (encoded as a small integer from 0 to 2)
-# list of completer functions (optional)
-# 
-
-def show_usage(cmd):
-    p = None
-    try: p = cmd.__doc__
-    except: pass
-    if p:
-        print >> sys.stderr, p
-    else:
-        syntax_err(cmd.__name__)
-
-def parse_line(lvl,s):
-    if not s: return True
-    if s[0].startswith('#'): return True
-    lvl.mark()
-    pt = lvl.parse_root
-    cmd = None
-    i = 0
-    for i in range(len(s)):
-        token = s[i]
-        if token in pt:
-            if type(pt[token]) == type(object):
-                lvl.new_level(pt[token],token)
-                pt = lvl.parse_root # move to the next level
-            else:
-                cmd = pt[token] # terminal symbol
-                break  # and stop parsing
-        else:
-            syntax_err(s[i:])
-            lvl.release()
-            return False
-    if cmd: # found a terminal symbol
-        if not user_prefs.check_skill_level(cmd[2]):
-            lvl.release()
-            skill_err(s[i])
-            return False
-        args = s[i+1:]
-        if not check_args(args,cmd[1]):
-            lvl.release()
-            show_usage(cmd[0])
-            return False
-        args = s[i:]
-        d = lambda: cmd[0](*args)
-        rv = d() # execute the command
-        lvl.release()
-        return rv != False
-    return True
-
-# three modes: interactive (no args supplied), batch (input from
-# a file), half-interactive (args supplied, but not batch)
-interactive = False
-batch = False
-inp_file = ''
-prompt = ''
-def cib_prompt():
-    return cib_in_use or "live"
-
-def usage():
-    print >> sys.stderr, """
-usage:
-    crm [-D display_type] [-f file] [-hF] [args]
-
-    Use crm without arguments for an interactive session.
-    Supply one or more arguments for a "single-shot" use.
-    Specify with -f a file which contains a script. Use '-' for
-    standard input or use pipe/redirection.
-
-    crm displays cli format configurations using a color scheme
-    and/or in uppercase. Pick one of "color" or "uppercase", or
-    use "-D color,uppercase" if you want colorful uppercase.
-    Get plain output by "-D plain". The default may be set in
-    user preferences (options).
-
-    -F stands for force, if set all operations will behave as if
-    force was specified on the line (e.g. configure commit).
-
-Examples:
-
-    # crm -f stopapp2.cli
-    # crm < stopapp2.cli
-    # crm resource stop global_www
-    # crm status 
-
-    """
-    sys.exit(1)
-
-hist_file = os.environ.get('HOME')+"/.crm_history"
-rc_file = os.environ.get('HOME')+"/.crm.rc"
-
-help_sys = HelpSystem()
-levels = Levels(TopLevel)
-this_node = os.uname()[1]
-cib_in_use = os.getenv(CibShadow().envvar)
-
-load_rc(rc_file)
-
-if not sys.stdin.isatty():
-    lineno = 0
-    batch = True
-else:
-    interactive = True
-
-import getopt
-try:
-    opts, args = getopt.getopt(sys.argv[1:], \
-        'hdf:FRD:', ("help","debug","file=",\
-        "force","regression-tests","display="))
-    for o,p in opts:
-        if o in ("-h","--help"):
-            usage()
-        elif o == "-d":
-            user_prefs.set_debug()
-        elif o == "-R":
-            regression_tests = True
-        elif o in ("-D","--display"):
-            user_prefs.set_output(p)
-        elif o in ("-F","--force"):
-            user_prefs.set_force()
-        elif o in ("-f","--file"):
-            batch = True
-            lineno = 0
-            inp_file = p
-except getopt.GetoptError,msg:
-    print msg
-    usage()
-
-if len(args) == 1 and args[0].startswith("conf"):
-    parse_line(levels,["configure"])
-    interactive = True
-elif len(args) > 0:
-    lineno = 0
-    interactive = False
-    if parse_line(levels,shlex.split(' '.join(args))):
-        # if the user entered a level, then just continue
-        if levels.previous():
-            if not inp_file and sys.stdin.isatty():
-                interactive = True
-        else:
-            sys.exit(0)
-    else:
-        sys.exit(1)
-
-if inp_file == "-":
-    pass
-elif inp_file:
-    try:
-        f = open(inp_file)
-    except IOError, msg:
-        common_err(msg)
-        usage()
-    sys.stdin = f
-
-if interactive:
-    compl_help = CompletionHelp()
-    readline.set_history_length(100)
-    readline.parse_and_bind("tab: complete")
-    readline.set_completer(completer)
-    readline.set_completer_delims(\
-        readline.get_completer_delims().replace('-','').replace('/','').replace('=',''))
-    try: readline.read_history_file(hist_file)
-    except: pass
-
-while True:
-    if interactive:
-        prompt = "crm(%s)%s# " % (cib_prompt(),levels.getprompt())
-    inp = multi_input(prompt)
-    if inp == None:
-        cmd_exit("eof")
-    try: parse_line(levels,shlex.split(inp))
-    except ValueError, msg:
-        common_err(msg)
-
-# vim:ts=4:sw=4:et: