diff --git a/shell/modules/cibstatus.py b/shell/modules/cibstatus.py index 4b641d6311..a4aa331b42 100644 --- a/shell/modules/cibstatus.py +++ b/shell/modules/cibstatus.py @@ -1,323 +1,326 @@ # 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 sys import os import re import time from singletonmixin import Singleton from vars import Vars from xmlutil import * from msg import * 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_id(n): try: n = n.parentNode except: return None if n.tagName != "node_state": return get_status_node_id(n) return n.getAttribute("id") def get_status_node(status_node,node): for n in status_node.childNodes: if not is_element(n) or n.tagName != "node_state": continue if n.getAttribute("id") == node: return n return None 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 o.getAttribute("interval") == interval or \ (interval == "-1" and o.getAttribute("interval") != "0"): l.append(o) return l def split_op(op): if op == "probe": return "monitor","0" elif op == "monitor": return "monitor","-1" elif op[0:8] == "monitor:": return "monitor",op[8:] return op,"0" def shadowfile(name): return "%s/shadow.%s" % (vars.crm_conf_dir, name) def cib_path(source): return source[0:7] == "shadow:" and shadowfile(source[7:]) or source class CibStatus(Singleton): ''' CIB status management ''' cmd_inject = "/dev/null 2>&1 crm_simulate -x %s -I %s" cmd_run = "2>&1 crm_simulate -R -x %s" cmd_simulate = "2>&1 crm_simulate -S -x %s" node_ops = { "online": "-u", "offline": "-d", "unclean": "-f", } def __init__(self): self.origin = "live" self.backing_file = "" # file to keep the live cib self.status_node = None self.doc = None self.cib = None self.reset_state() def _load_cib(self,source): if source == "live": if not self.backing_file: self.backing_file = cib2tmp() if not self.backing_file: return None,None + else: + cibdump2file(self.backing_file) f = self.backing_file else: f = cib_path(source) return read_cib(file2doc,f) 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 return True def reset_state(self): self.modified = False self.quorum = '' self.node_changes = {} self.op_changes = {} return True def source_file(self): if self.origin == "live": return self.backing_file else: return cib_path(self.origin) def status_node_list(self): - if not self.status_node and not self._load(self.origin): + if not self.get_status(): 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): + if not self.get_status(): 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 self.backing_file: os.unlink(self.backing_file) self.backing_file = "" if not self._load(source): common_err("the cib contains no status") return False self.reset_state() 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 = 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 = 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 _crm_simulate(self, cmd, nograph, scores, utilization, verbosity): if verbosity: cmd = "%s -%s" % (cmd,verbosity.upper()) if scores: cmd = "%s -s" % cmd if utilization: cmd = "%s -U" % cmd if user_prefs.dotty and not nograph: fd,dotfile = mkstemp() cmd = "%s -D %s" % (cmd,dotfile) else: dotfile = None rc = ext_cmd(cmd % self.source_file()) if dotfile: show_dot_graph(dotfile) vars.tmpfiles.append(dotfile) return rc == 0 def run(self, nograph, scores, utilization, verbosity): return self._crm_simulate(self.cmd_run, \ nograph, scores, utilization, verbosity) def simulate(self, nograph, scores, utilization, verbosity): return self._crm_simulate(self.cmd_simulate, \ nograph, scores, utilization, verbosity) def get_status(self): ''' Return the status section node. ''' - if not self.status_node and not self._load(self.origin): + if (not self.status_node or \ + (self.origin == "live" and not self.modified)) \ + 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] if self.quorum: print "quorum:",self.quorum return True def show(self): ''' Page the "pretty" XML of the status section. ''' - if not self.status_node: - common_info("no status loaded yet") + if not self.get_status(): return False page_string(self.status_node.toprettyxml(user_prefs.xmlindent)) return True def inject(self,opts): return ext_cmd("%s %s" % \ (self.cmd_inject % (self.source_file(), self.source_file()), opts)) def set_quorum(self, v): rc = self.inject("--quorum=%s" % (v and "true" or "false")) if rc != 0: return False self._load(self.origin) self.quorum = v and "true" or "false" self.modified = True 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): + if not self.get_status(): return False if not state in self.node_ops: common_err("unknown state %s" % state) return False 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 rc = self.inject("%s %s" % (self.node_ops[state], node)) if rc != 0: return False self._load(self.origin) self.node_changes[node] = state self.modified = True return True def edit_op(self,op,rsc,rc_code,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): + if not self.get_status(): return False l_op,l_int = split_op(op) op_nodes = get_status_ops(self.status_node,rsc,l_op,l_int,node) if l_int == "-1" and len(op_nodes) != 1: common_err("need interval for the monitor op") return False if node == '' and len(op_nodes) != 1: if op_nodes: nodelist = [get_status_node_id(x) for x in op_nodes] common_err("operation %s found at %s" % (op,' '.join(nodelist))) else: common_err("operation %s not found" % op) return False # either the op is fully specified (maybe not found) # or we found exactly one op_node if len(op_nodes) == 1: op_node = op_nodes[0] if not node: node = get_status_node_id(op_node) if not node: common_err("node not found for the operation %s" % op) return False if l_int == "-1": l_int = op_node.getAttribute("interval") op_op = op_status == "0" and "-i" or "-F" rc = self.inject("%s %s_%s_%s@%s=%s" % \ (op_op, rsc, l_op, l_int, node, rc_code)) if rc != 0: return False self.op_changes[node+":"+rsc+":"+op] = "rc="+rc_code if op_status: self.op_changes[node+":"+rsc+":"+op] += "," "op-status="+op_status self._load(self.origin) self.modified = True return True vars = Vars.getInstance() # vim:ts=4:sw=4:et: diff --git a/shell/modules/utils.py b/shell/modules/utils.py index da5e61f567..9679336606 100644 --- a/shell/modules/utils.py +++ b/shell/modules/utils.py @@ -1,318 +1,329 @@ # 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 os from tempfile import mkstemp import subprocess import re import glob from userprefs import Options, UserPrefs from msg import * def is_program(prog): return subprocess.call("which %s >/dev/null 2>&1"%prog, shell=True) == 0 def ask(msg): # if there's no terminal, no use asking and default to "no" if not sys.stdin.isatty(): return False 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 verify_boolean(opt): return opt.lower() in ("yes","true","on") or \ opt.lower() in ("no","false","off") def is_boolean_true(opt): return opt.lower() in ("yes","true","on") 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 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] 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 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 filter_string(cmd,s,stderr_on = True): rc = -1 # command failed if stderr_on: stderr = None else: stderr = subprocess.PIPE cmd = add_sudo(cmd) p = subprocess.Popen(cmd, shell=True, \ stdin = subprocess.PIPE, \ stdout = subprocess.PIPE, stderr = stderr) try: outp = p.communicate(s)[0] p.wait() rc = p.returncode except IOError, msg: common_err(msg) return rc,outp 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 str2file(s,fname): + ''' + Write a string to a file. + ''' + try: f = open(fname,"w") + except IOError, msg: + common_err(msg) + return False + f.write(s) + f.close() + return True 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 show_dot_graph(dotfile): p = subprocess.Popen("%s %s" % (user_prefs.dotty,dotfile), shell=True, bufsize=0, stdin=None, stdout=None, stderr=None, close_fds=True) common_info("starting %s to show transition graph"%user_prefs.dotty) def ext_cmd(cmd): if options.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 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) 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 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 options.interactive: print s else: opts = "" if user_prefs.pager == "less": opts = "-R" pipe_string("%s %s" % (user_prefs.pager,opts), s) 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 def find_value(pl,name): for n,v in pl: if n == name: return v return None 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] user_prefs = UserPrefs.getInstance() options = Options.getInstance() # vim:ts=4:sw=4:et: diff --git a/shell/modules/xmlutil.py b/shell/modules/xmlutil.py index 4cc8e1d4dc..8899a3cc41 100644 --- a/shell/modules/xmlutil.py +++ b/shell/modules/xmlutil.py @@ -1,752 +1,762 @@ # 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 os import subprocess import xml.dom.minidom from userprefs import Options, UserPrefs from vars import Vars from msg import * from utils import * 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 file2doc(s): try: f = open(s,'r') except IOError, msg: common_err(msg) return None doc = xmlparse(f) f.close() return doc cib_dump = "cibadmin -Ql" +def cibdump2file(fname): + cmd = add_sudo(cib_dump) + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + try: + s = ''.join(p.stdout) + p.wait() + except IOError, msg: + common_err(msg) + return None + return str2file(s,fname) def cib2tmp(): cmd = add_sudo(cib_dump) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) try: tmpf = str2tmp(''.join(p.stdout)) p.wait() except IOError, msg: common_err(msg) return None return tmpf 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 cib_piped = "cibadmin -p" def commit_rsc(node): "Replace a resource definition using cibadmin -R" rc = pipe_string("%s -R -o %s" % \ (cib_piped, "resources"), node.toxml()) return rc == 0 def get_conf_elem(doc, tag): try: return doc.getElementsByTagName(tag)[0] except: return None 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 sanity_check_nvpairs(id,node,attr_list): rc = 0 for nvpair in node.childNodes: if not is_element(nvpair) or nvpair.tagName != "nvpair": continue n = nvpair.getAttribute("name") if n and not n in attr_list: common_err("%s: attribute %s does not exist" % (id,n)) rc |= user_prefs.get_check_rc() return rc def sanity_check_meta(id,node,attr_list): rc = 0 if not node or not attr_list: return rc for c in node.childNodes: if not is_element(c): continue if c.tagName == "meta_attributes": rc |= sanity_check_nvpairs(id,c,attr_list) return rc def get_interesting_nodes(node,nodes): for c in node.childNodes: if is_element(c) and c.tagName in vars.cib_cli_map: nodes.append(c) get_interesting_nodes(c,nodes) return nodes def resources_xml(): return cibdump2doc("resources") def rsc2node(id): doc = resources_xml() if not doc: return None nodes = get_interesting_nodes(doc,[]) for n in nodes: if is_resource(n) and n.getAttribute("id") == id: return n def get_meta_param(id,param): rsc_meta_show = "crm_resource --meta -r '%s' -g '%s'" return get_stdout(rsc_meta_show % (id,param), stderr_on = False) def is_live_cib(): '''We working with the live cluster?''' return not vars.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 rsc_status = "crm_resource -W -r '%s'" test_id = rsc_clone(id) or id outp = get_stdout(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 get_topmost_rsc(node): ''' Return a topmost node which is a resource and contains this resource ''' if is_container(node.parentNode): return get_topmost_rsc(node.parentNode) return node 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 "" 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" def is_emptynvpairs(node): if is_element(node) and node.tagName in vars.nvpairs_tags: for a in vars.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 vars.clonems_tags def is_container(node): return is_element(node) \ and node.tagName in vars.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 vars.resource_tags def is_child_rsc(node): return is_element(node) \ and node.tagName in vars.children_tags def is_constraint(node): return is_element(node) \ and node.tagName in vars.constraint_tags def is_defaults(node): return is_element(node) \ and node.tagName in vars.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 vars.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 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 vars.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 match_list = { "node": ("uname",), "crm_config": (), "rsc_defaults": (), "op_defaults": (), "cluster_property_set": (), "instance_attributes": (), "meta_attributes": (), "utilization": (), "operations": (), "nvpair": ("name",), "op": ("name","interval"), "rule": ("score","score-attribute","role"), "expression": ("attribute","operation","value"), } def add_comment(node,s): ''' Add comment s to node from doc. ''' if not node or not s: return comm_node = node.ownerDocument.createComment(s) firstelem = None for n in node.childNodes: if is_element(n): firstelem = n break node.insertBefore(comm_node, firstelem) def stuff_comments(node,comments): for s in comments: add_comment(node,s) def fix_comments(node): 'Make sure that comments start with #' cnodes = [x for x in node.childNodes if is_comment(x)] for n in cnodes: n.data = n.data.strip() if not n.data.startswith("#"): n.data = "# %s" % n.data 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") if not oldnode: return None #print " in:",oldnode.tagName,oldnode.getAttribute("id") try: attr_list = list(match_list[node.tagName]) except KeyError: attr_list = [] if node.getAttribute("id") and oldnode.getAttribute("id"): attr_list.append("id") 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 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 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] if user_prefs.get_sort_elems(): l.sort(cmp = cmp) else: l = [obj for obj in cl if obj.obj_type == obj_type] if user_prefs.get_sort_elems(): 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 is_resource_cli(s): return s in olist(vars.resource_cli_names) def is_constraint_cli(s): return s in olist(vars.constraint_cli_names) def referenced_resources(node): if not is_constraint(node): return [] xml_obj_type = node.tagName if xml_obj_type == "rsc_location": node_list = node.getElementsByTagName("rsc") elif node.getElementsByTagName("resource_ref"): # resource sets node_list = node.getElementsByTagName("resource_ref") elif xml_obj_type == "rsc_colocation": node_list = node.getElementsByTagName("rsc") + \ node.getElementsByTagName("with-rsc") elif xml_obj_type == "rsc_order": node_list = node.getElementsByTagName("first") + \ node.getElementsByTagName("then") return [x.getAttribute("id") for x in node_list] 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 vars.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 vars.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(format = -1) 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 vars.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 def get_rsc_children_ids(node): return [x.getAttribute("id") \ for x in node.childNodes if is_child_rsc(x)] def get_rscop_defaults_meta_node(node): for c in node.childNodes: if not is_element(c) or c.tagName != "meta_attributes": continue return c return None 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 new_cib_element(node,tagname,id_pfx): base_id = node.getAttribute("id") newnode = node.ownerDocument.createElement(tagname) newnode.setAttribute("id", "%s-%s" % (base_id,id_pfx)) node.appendChild(newnode) return newnode def get_attr_in_set(node,attr): for c in node.childNodes: if not is_element(c): continue if c.tagName == "nvpair" and c.getAttribute("name") == attr: return c return None def set_attr(node,attr,value): ''' Set an attribute in the attribute set. ''' nvpair = get_attr_in_set(node,attr) if not nvpair: nvpair = new_cib_element(node,"nvpair",attr) nvpair.setAttribute("name",attr) nvpair.setAttribute("value",value) def get_set_nodes(node,setname,create = 0): 'Return the attributes set nodes (create one if requested)' l = [] for c in node.childNodes: if not is_element(c): continue if c.tagName == setname: l.append(c) if l: return l if create: l.append(new_cib_element(node,setname,setname)) return l 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 merge_nvpairs(dnode,snode): rc = False add_children = [] for c in snode.childNodes: if c.tagName == "nvpair": dc = lookup_node(c,dnode) if dc: dc.setAttribute("value",c.getAttribute("value")) else: add_children.append(c) rc = True for c in add_children: dnode.appendChild(c) return rc def merge_nodes(dnode,snode): ''' Import elements from snode into dnode. If an element is attributes set (vars.nvpairs_tags), then merge nvpairs by the name attribute. Otherwise, replace the whole element. (TBD) ''' #print "1:",dnode.toprettyxml() #print "2:",snode.toprettyxml() #vars.nvpairs_tags rc = False # any changes done? if not dnode or not snode: return rc for c in snode.childNodes: dc = lookup_node(c,dnode) if not dc: continue if dc.tagName in vars.nvpairs_tags: rc = rc or merge_nvpairs(dc,c) return rc user_prefs = UserPrefs.getInstance() vars = Vars.getInstance() # vim:ts=4:sw=4:et: