diff --git a/shell/modules/cibstatus.py b/shell/modules/cibstatus.py index 6cb67ab828..c29fb01a9c 100644 --- a/shell/modules/cibstatus.py +++ b/shell/modules/cibstatus.py @@ -1,331 +1,333 @@ # 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 _cib_path(self,source): if source[0:7] == "shadow:": return shadowfile(source[7:]) else: return source 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 self.reset_state() 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.get_status(): return return [x.getAttribute("id") for x in self.doc.getElementsByTagName("node_state")] def status_rsc_list(self): 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.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): + # actions is ignored + def run(self, nograph, scores, utilization, actions, verbosity): return self._crm_simulate(self.cmd_run, \ nograph, scores, utilization, verbosity) - def simulate(self, nograph, scores, utilization, verbosity): + # actions is ignored + def simulate(self, nograph, scores, utilization, actions, 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 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.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.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.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/ui.py.in b/shell/modules/ui.py.in index 97407a8227..f1fcf08363 100644 --- a/shell/modules/ui.py.in +++ b/shell/modules/ui.py.in @@ -1,1985 +1,1982 @@ # 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 re import os import readline import shlex import time import bz2 from help import HelpSystem, cmd_help from vars import Vars from levels import Levels from cibconfig import mkset_obj, CibFactory from cibstatus import CibStatus from template import LoadTemplate from cliformat import nvpairs2list from ra import * from msg import * from utils import * from xmlutil import * def cmd_end(cmd,dir = ".."): "Go up one level." levels.droplevel() def cmd_exit(cmd): "Exit the crm program" cmd_end(cmd) if options.interactive and not options.batch: print "bye" try: readline.write_history_file(hist_file) except: pass for f in vars.tmpfiles: os.unlink(f) sys.exit() class UserInterface(object): ''' Stuff common to all user interface classes. ''' global_cmd_aliases = { "quit": ("bye","exit"), "end": ("cd","up"), } 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 = self.global_cmd_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) 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["sort-elements"] = (self.set_sort_elements,(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 set_sort_elements(self,cmd,opt): "usage: sort-elements {yes|no}" if not verify_boolean(opt): common_err("%s: bad boolean option"%opt) return True return user_prefs.set_sort_elems(opt) 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(vars.rc_file) def end_game(self, no_questions_asked = False): if no_questions_asked and not options.interactive: self.save_options("save") def listshadows(): return stdout2list("ls %s | fgrep shadow. | sed 's/^shadow\.//'" % vars.crm_conf_dir) def shadowfile(name): return "%s/shadow.%s" % (vars.crm_conf_dir, name) def shadow2doc(name): return file2doc(shadowfile(name)) class CibShadow(UserInterface): ''' CIB shadow management class ''' 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 _find_pe(self,infile): 'Find a pe input' for p in ("%s/%s", "%s/%s.bz2", "%s/pe-*-%s.bz2"): fl = glob.glob(p % (vars.pe_dir,infile)) if fl: break if not fl: common_err("no %s pe input file"%infile) return '' if len(fl) > 1: common_err("more than one %s pe input file: %s" % \ (infile,' '.join(fl))) return '' return fl[0] def pe_import(self,cmd,infile,name = None): "usage: import {|} []" if name and not is_filename_sane(name): return False # where's the input? if not os.access(infile,os.F_OK): if "/" in infile: common_err("%s: no such file"%infile) return False infile = self._find_pe(infile) if not infile: return False if not name: name = os.path.basename(infile) # read input try: f = open(infile) except IOError,msg: common_err("open: %s"%msg) return s = ''.join(f) f.close() # decompresed and rename shadow if it ends with .bz2 if infile.endswith(".bz2"): name = name.replace(".bz2","") s = bz2.decompress(s) # copy input to the shadow try: f = open(shadowfile(name), "w") except IOError,msg: common_err("open: %s"%msg) return f.write(s) f.close() # use the shadow and load the status from there return self.use("use",name,"withstatus") def delete(self,cmd,name): "usage: delete " if not is_filename_sane(name): return False if vars.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) 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 options.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 if not name or name == "live": os.unsetenv(vars.shadow_envvar) vars.cib_in_use = "" if withstatus: cib_status.load("live") else: os.putenv(vars.shadow_envvar,name) vars.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 = vars.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 listtemplates(): l = [] for f in os.listdir(vars.tmpl_dir): if os.path.isfile("%s/%s" % (vars.tmpl_dir,f)): l.append(f) return l def listconfigs(): l = [] for f in os.listdir(vars.tmpl_conf_dir): if os.path.isfile("%s/%s" % (vars.tmpl_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. ''' 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(vars.tmpl_conf_dir): try: os.makedirs(vars.tmpl_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" % (vars.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" % (vars.tmpl_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 err_buf.start_tmp_lineno() rc = True for inp in f: err_buf.incr_lineno() 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) err_buf.stop_tmp_lineno() f.close() if not rc: return '' return self.generate(l,user_data) def new(self,cmd,name,*args): "usage: new