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: