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 -# -# 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__,]] - === ... - Long help text. - ... - [[cmdhelp__,]] - - Help for the level itself is like this: - - [[cmdhelp_,]] - ''' - 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 []" - 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: operator | administrator | expert""" - return user_prefs.set_skill_level(skill_level) - def set_editor(self,cmd,prog): - "usage: editor " - return user_prefs.set_editor(prog) - def set_pager(self,cmd,prog): - "usage: pager " - return user_prefs.set_pager(prog) - def set_crm_user(self,cmd,user = ''): - "usage: user []" - return user_prefs.set_crm_user(user) - def set_output(self,cmd,otypes): - "usage: output " - return user_prefs.set_output(otypes) - def set_colors(self,cmd,scheme): - "usage: colorscheme " - return user_prefs.set_colors(scheme) - def set_check_frequency(self,cmd,freq): - "usage: check-frequence " - return user_prefs.set_check_freq(freq) - def set_check_mode(self,cmd,mode): - "usage: check-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 &1" % self.extcmd) - except os.error: - no_prog_err(self.extcmd) - return False - return True - def new(self,cmd,name,*args): - "usage: new [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 " - 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 " - 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 " - 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 [] [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