diff --git a/shell/modules/ra.py.in b/shell/modules/ra.py.in index 2b4f14319e..b0c4d27e44 100644 --- a/shell/modules/ra.py.in +++ b/shell/modules/ra.py.in @@ -1,659 +1,660 @@ # 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 sys import subprocess import copy import xml.dom.minidom import re import glob from userprefs import Options, UserPrefs from cache import WCache from vars import Vars from utils import * from msg import * # # 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 classes.' 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) 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_if(): if vars.ra_if: return vars.ra_if - vars.ra_if = RaLrmd() - if not vars.ra_if.good: + if getuser() in ("root",vars.crm_daemon_user): + vars.ra_if = RaLrmd() + if not vars.ra_if or not vars.ra_if.good: vars.ra_if = RaOS() return vars.ra_if 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 get_pe_meta(): if not vars.pe_metadata: vars.pe_metadata = RAInfo("pengine","metadata") return vars.pe_metadata def get_crmd_meta(): if not vars.crmd_metadata: vars.crmd_metadata = RAInfo("crmd","metadata") vars.crmd_metadata.set_advanced_params(vars.crmd_advanced) return vars.crmd_metadata def get_stonithd_meta(): if not vars.stonithd_metadata: vars.stonithd_metadata = RAInfo("stonithd","metadata") return vars.stonithd_metadata def get_cib_meta(): if not vars.cib_metadata: vars.cib_metadata = RAInfo("cib","metadata") return vars.cib_metadata def get_properties_meta(): if not vars.crm_properties_metadata: get_pe_meta() get_crmd_meta() get_cib_meta() vars.crm_properties_metadata = copy.deepcopy(vars.crmd_metadata) vars.crm_properties_metadata.add_ra_params(vars.pe_metadata) vars.crm_properties_metadata.add_ra_params(vars.cib_metadata) return vars.crm_properties_metadata def get_properties_list(): try: return get_properties_meta().params().keys() except: return [] def prog_meta(prog): ''' Do external program metadata. ''' 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.advanced_params = [] 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 set_advanced_params(self, l): self.advanced_params = l def filter_crmd_attributes(self): for n in self.ra_node.getElementsByTagName("parameter"): if not n.getAttribute("name") in vars.crmd_user_attributes: n.parentNode.removeChild(n) def add_ra_params(self,ra): ''' Add parameters from another RAInfo instance. ''' try: if not self.mk_ra_node() or not ra.mk_ra_node(): return except: 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 ra.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: self.error("could not parse meta-data: %s" % '\n'.join(meta)) 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_ra_params(get_stonithd_meta()) 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 completion_params(self): ''' Extra method for completion, for we want to filter some (advanced) parameters out. And we want this to be fast. ''' if not self.mk_ra_node(): return None return [c.getAttribute("name") for c in self.ra_node.getElementsByTagName("parameter") if c.getAttribute("name") and c.getAttribute("name") not in self.advanced_params ] 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() try: return d[pname]["default"] except: return None 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 get_adv_timeout(self, op, node = None): if node and op == "monitor": name = monitor_name_node(node) else: name = op try: return self.actions()[name]["timeout"] except: return None def sanity_check_ops(self, id, ops, default_timeout): ''' 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 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 if "timeout" in n_ops[op]: v = n_ops[op]["timeout"] timeout_string = "specified timeout" else: v = default_timeout timeout_string = "default timeout" if crm_msec(v) < 0: continue if crm_time_cmp(adv_timeout,v) > 0: common_warn("%s: %s %s for %s is smaller than the advised %s" % \ (id,timeout_string,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 vars.meta_progs: 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) # # resource type definition # def ra_type_validate(s, ra_class, provider, rsc_type): ''' Only ocf ra class supports providers. ''' if not rsc_type: common_err("bad resource type specification %s"%s) return False if ra_class == "ocf": if not provider: common_err("provider could not be determined for %s"%s) return False else: if provider: common_warn("ra class %s does not support providers"%ra_class) return True return True def disambiguate_ra_type(s): ''' Unravel [class:[provider:]]type ''' l = s.split(':') if not l or len(l) > 3: return None if len(l) == 3: return l elif len(l) == 2: ra_class,ra_type = l else: ra_class = "ocf" ra_type = l[0] ra_provider = '' if ra_class == "ocf": pl = ra_providers(ra_type,ra_class) if pl and len(pl) == 1: ra_provider = pl[0] elif not pl: ra_provider = 'heartbeat' return ra_class,ra_provider,ra_type wcache = WCache.getInstance() vars = Vars.getInstance() # vim:ts=4:sw=4:et: