diff --git a/shell/modules/completion.py b/shell/modules/completion.py index 67a69109e1..c8b13371ba 100644 --- a/shell/modules/completion.py +++ b/shell/modules/completion.py @@ -1,490 +1,478 @@ # 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # import os import time import copy import readline from cibconfig import CibFactory from cibstatus import CibStatus from levels import Levels from ra import * from vars import Vars from utils import * from xmlutil import * class CompletionHelp(object): ''' Print some help on whatever last word in the line. ''' timeout = 60 # don't print again and again def __init__(self): self.laststamp = 0 self.lastitem = '' def help(self,f,*args): words = readline.get_line_buffer().split() if not words: return key = words[-1] if key.endswith('='): key = key[0:-1] if self.lastitem == key and \ time.time() - self.laststamp < self.timeout: return help_s = f(key,*args) if help_s: print "\n%s" % help_s print "%s%s" % (vars.prompt,readline.get_line_buffer()), self.laststamp = time.time() self.lastitem = key def attr_cmds(idx,delimiter = False): if delimiter: return ' ' return ["delete","set","show"] def nodes_list(idx,delimiter = False): if delimiter: return ' ' return listnodes() def shadows_list(idx,delimiter = False): if delimiter: return ' ' return listshadows() -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 templates_list(idx,delimiter = False): if delimiter: return ' ' return listtemplates() def config_list(idx,delimiter = False): if delimiter: return ' ' return listconfigs() def config_list_method(idx,delimiter = False): if delimiter: return ' ' return listconfigs() + ["replace","update"] def shadows_live_list(idx,delimiter = False): if delimiter: return ' ' return listshadows() + ['live'] def rsc_list(idx,delimiter = False): if delimiter: return ' ' doc = resources_xml() if not doc: return [] nodes = get_interesting_nodes(doc,[]) return [x.getAttribute("id") for x in nodes if is_resource(x)] def null_list(idx,delimiter = False): if delimiter: return ' ' return [] def loop(idx,delimiter = False): "just a marker in a list" pass def id_xml_list(idx,delimiter = False): if delimiter: return ' ' return cib_factory.id_list() + ['xml','changed'] def id_list(idx,delimiter = False): if delimiter: return ' ' return cib_factory.id_list() def f_prim_id_list(idx,delimiter = False): if delimiter: return ' ' return cib_factory.f_prim_id_list() def f_children_id_list(idx,delimiter = False): if delimiter: return ' ' return cib_factory.f_children_id_list() def rsc_id_list(idx,delimiter = False): if delimiter: return ' ' return cib_factory.rsc_id_list() def node_id_list(idx,delimiter = False): if delimiter: return ' ' return cib_factory.node_id_list() def node_attr_keyw_list(idx,delimiter = False): if delimiter: return ' ' return vars.node_attributes_keyw def status_node_list(idx,delimiter = False): if delimiter: return ' ' return cib_status.status_node_list() def status_rsc_list(idx,delimiter = False): if delimiter: return ' ' return cib_status.status_rsc_list() def node_states_list(idx,delimiter = False): if delimiter: return ' ' return vars.node_states def ra_operations_list(idx,delimiter = False): if delimiter: return ' ' return vars.ra_operations def lrm_exit_codes_list(idx,delimiter = False): if delimiter: return ' ' return vars.lrm_exit_codes.keys() def lrm_status_codes_list(idx,delimiter = False): if delimiter: return ' ' return vars.lrm_status_codes.keys() def skills_list(idx,delimiter = False): if delimiter: return ' ' return user_prefs.skill_levels.keys() def ra_classes_list(idx,delimiter = False): if delimiter: return ':' return ra_classes() # # completion for primitives including help for parameters # (help also available for properties) # def get_primitive_type(words): try: idx = words.index("primitive") + 2 type_word = words[idx] except: type_word = '' return type_word def ra_type_list(toks,idx,delimiter): if idx == 2: if toks[0] == "ocf": dchar = ':' l = ra_providers_all() else: dchar = ' ' l = ra_types(toks[0]) elif idx == 3: dchar = ' ' if toks[0] == "ocf": l = ra_types(toks[0],toks[1]) else: l = ra_types(toks[0]) if delimiter: return dchar return l def prim_meta_attr_list(idx,delimiter = False): if delimiter: return '=' return vars.rsc_meta_attributes def op_attr_list(idx,delimiter = False): if delimiter: return '=' return vars.op_attributes def operations_list(): return vars.op_cli_names def prim_complete_meta(ra,delimiter = False): if delimiter: return '=' return prim_meta_attr_list(0,delimiter) def prim_complete_op(ra,delimiter): words = split_buffer() if (readline.get_line_buffer()[-1] == ' ' and words[-1] == "op") \ or (readline.get_line_buffer()[-1] != ' ' and words[-2] == "op"): dchar = ' ' l = operations_list() else: if readline.get_line_buffer()[-1] == '=': dchar = ' ' l = [] else: dchar = '=' l = op_attr_list() if delimiter: return dchar return l def prim_complete_params(ra,delimiter): if readline.get_line_buffer()[-1] == '=': dchar = ' ' l = [] else: dchar = '=' l = ra.completion_params() if delimiter: return dchar return l def prim_params_info(key,ra): return ra.meta_parameter(key) def meta_attr_info(key,ra): pass def op_attr_info(key,ra): pass def get_lastkeyw(words,keyw): revwords = copy.copy(words) revwords.reverse() for w in revwords: if w in keyw: return w def primitive_complete_complex(idx,delimiter = False): ''' This completer depends on the content of the line, i.e. on previous tokens, in particular on the type of the RA. ''' completers_set = { "params": (prim_complete_params, prim_params_info), "meta": (prim_complete_meta, meta_attr_info), "op": (prim_complete_op, op_attr_info), } # manage the resource type words = readline.get_line_buffer().split() type_word = get_primitive_type(words) toks = type_word.split(':') if toks[0] != "ocf": idx += 1 if idx in (2,3): return ra_type_list(toks,idx,delimiter) # create an ra object ra = None ra_class,provider,rsc_type = disambiguate_ra_type(type_word) if ra_type_validate(type_word,ra_class,provider,rsc_type): ra = RAInfo(ra_class,rsc_type,provider) keywords = completers_set.keys() if idx == 4: if delimiter: return ' ' return keywords lastkeyw = get_lastkeyw(words,keywords) if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ': if not delimiter and lastkeyw and \ readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1: compl_help.help(completers_set[lastkeyw][1],ra) if delimiter: return ' ' return ['*'] else: if lastkeyw: return completers_set[lastkeyw][0](ra,delimiter) def property_complete(idx,delimiter = False): ''' This completer depends on the content of the line, i.e. on previous tokens. ''' ra = get_properties_meta() words = readline.get_line_buffer().split() if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ': if not delimiter and \ readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1: compl_help.help(prim_params_info,ra) if delimiter: return ' ' return ['*'] else: return prim_complete_params(ra,delimiter) # # core completer stuff # def lookup_dynamic(fun_list,idx,f_idx,words): if not fun_list: return [] if fun_list[f_idx] == loop: f_idx -= 1 f = fun_list[f_idx] w = words[0] wordlist = f(idx) delimiter = f(idx,1) if len(wordlist) == 1 and wordlist[0] == '*': return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:]) elif len(words) == 1: return [x+delimiter for x in wordlist if x.startswith(w)] return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:]) def lookup_words(ctab,words): if not ctab: return [] if type(ctab) == type(()): return lookup_dynamic(ctab,0,0,words) if len(words) == 1: return [x+' ' for x in ctab if x.startswith(words[0])] elif words[0] in ctab.keys(): return lookup_words(ctab[words[0]],words[1:]) return [] def split_buffer(): p = readline.get_line_buffer() p = p.replace(':',' ').replace('=',' ') return p.split() def completer(txt,state): levels = Levels.getInstance() words = split_buffer() if readline.get_begidx() == readline.get_endidx(): words.append('') matched = lookup_words(levels.completion_tab,words) matched.append(None) return matched[state] def setup_readline(): readline.set_history_length(100) readline.parse_and_bind("tab: complete") readline.set_completer(completer) readline.set_completer_delims(\ readline.get_completer_delims().replace('-','').replace('/','').replace('=','')) try: readline.read_history_file(vars.hist_file) except: pass # # a dict of completer functions # (feel free to add more completers) # completer_lists = { "options" : { "skill-level" : (skills_list,), "editor" : None, "pager" : None, "user" : None, "output" : None, "colorscheme" : None, "check-frequency" : None, "check-mode" : None, "sort-elements" : None, "save" : None, "show" : None, }, "cib" : { "new" : None, "delete" : (shadows_list,), "reset" : (shadows_list,), "commit" : (shadows_list,), "use" : (shadows_live_list,), "diff" : None, "list" : None, "import" : None, "cibstatus" : None, }, "template" : { "new" : (null_list,templates_list,loop), "load" : (config_list,), "edit" : (config_list,), "delete" : (config_list,), "show" : (config_list,), "apply" : (config_list_method,config_list), "list" : None, }, "resource" : { "status" : (rsc_list,), "start" : (rsc_list,), "stop" : (rsc_list,), "restart" : (rsc_list,), "promote" : (rsc_list,), "demote" : (rsc_list,), "manage" : (rsc_list,), "unmanage" : (rsc_list,), "migrate" : (rsc_list,nodes_list), "unmigrate" : (rsc_list,), "param" : (rsc_list,attr_cmds), "meta" : (rsc_list,attr_cmds), "utilization" : (rsc_list,attr_cmds), "failcount" : (rsc_list,attr_cmds,nodes_list), "cleanup" : (rsc_list,nodes_list), "refresh" : (nodes_list,), "reprobe" : (nodes_list,), }, "node" : { "status" : (nodes_list,), "show" : (nodes_list,), "standby" : (nodes_list,), "online" : (nodes_list,), "fence" : (nodes_list,), "delete" : (nodes_list,), "clearstate" : (nodes_list,), "attribute" : (nodes_list,attr_cmds), "utilization" : (nodes_list,attr_cmds), "status-attr" : (nodes_list,attr_cmds), }, "ra" : { "classes" : None, "list" : None, "providers" : None, "meta" : None, }, "cibstatus" : { "show" : None, "save" : None, "load" : None, "origin" : None, "node" : (status_node_list,node_states_list), "op" : (ra_operations_list,status_rsc_list,lrm_exit_codes_list,lrm_status_codes_list,status_node_list), "run" : None, "simulate" : None, "quorum" : None, }, "configure" : { "erase" : None, "verify" : None, "refresh" : None, "ptest" : None, "commit" : None, "upgrade" : None, "show" : (id_xml_list,id_list,loop), "edit" : (id_xml_list,id_list,loop), "filter" : (null_list,id_xml_list,id_list,loop), "delete" : (id_list,loop), "default-timeouts" : (id_list,loop), "rename" : (id_list,id_list), "save" : None, "load" : None, "node" : (node_id_list,node_attr_keyw_list), "primitive" : (null_list,ra_classes_list,primitive_complete_complex,loop), "group" : (null_list,f_prim_id_list,loop), "clone" : (null_list,f_children_id_list), "ms" : (null_list,f_children_id_list), "location" : (null_list,rsc_id_list), "colocation" : (null_list,null_list,rsc_id_list,loop), "order" : (null_list,null_list,rsc_id_list,loop), "property" : (property_complete,loop), "rsc_defaults" : (prim_complete_meta,loop), "op_defaults" : (op_attr_list,loop), "xml" : None, "monitor" : None, "ra" : None, "cib" : None, "cibstatus" : None, "template" : None, "_test" : None, "_regtest" : None, "_objects" : None, }, } def get_completer_list(level,cmd): 'Return a list of completer functions.' try: return completer_lists[level][cmd] except: return None compl_help = CompletionHelp() user_prefs = UserPrefs.getInstance() vars = Vars.getInstance() cib_status = CibStatus.getInstance() cib_factory = CibFactory.getInstance() # vim:ts=4:sw=4:et: diff --git a/shell/modules/utils.py b/shell/modules/utils.py index 13e4db6744..240c37c000 100644 --- a/shell/modules/utils.py +++ b/shell/modules/utils.py @@ -1,416 +1,429 @@ # 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # import os import pwd from tempfile import mkstemp import subprocess import re import glob import time 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_help_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] def setup_aliases(obj): for cmd in obj.cmd_aliases.keys(): for alias in obj.cmd_aliases[cmd]: obj.cmd_table[alias] = obj.cmd_table[cmd] def getpwdent(): try: euid = os.geteuid() except Exception, msg: common_err(msg) return None try: pwdent = pwd.getpwuid(euid) except Exception, msg: common_err(msg) return None return pwdent def getuser(): user = os.getenv("USER") if not user: try: return getpwdent()[0] except: return None else: return user def gethomedir(): homedir = os.getenv("HOME") if not homedir: try: return getpwdent()[5] except: return None else: return homedir 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 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 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 wait4dc(what = "", show_progress = True): ''' Wait for the DC to get into the S_IDLE state. This should be invoked only after a CIB modification which would exercise the PE. Parameter "what" is whatever the caller wants to be printed if showing progress. It is assumed that the DC is already in a different state, usually it should be either PENGINE or TRANSITION. This assumption may not be true, but there's a high chance that it is since crmd should be faster to move through states than this shell. Further, it may also be that crmd already calculated the new graph, did transition, and went back to the idle state. This may in particular be the case if the transition turned out to be empty. Tricky. Though in practice it shouldn't be an issue. There's no timeout, as we expect the DC to eventually becomes idle. ''' cmd = "crmadmin -D" s = get_stdout(add_sudo(cmd)) if not s.startswith("Designated"): common_warn("%s unexpected output: %s" % (cmd,s)) return False dc = s.split()[-1] if not dc: common_warn("can't find DC in: %s" % s) return False cmd = "crmadmin -S %s" % dc cnt = 0 output_started = 0 while True: s = get_stdout(add_sudo(cmd)) if not s.startswith("Status"): common_warn("%s unexpected output: %s" % (cmd,s)) return False try: dc_status = s.split()[-2] except: common_warn("%s unexpected output: %s" % (cmd,s)) return False if dc_status == "S_IDLE": if output_started: sys.stderr.write(" done\n") return True time.sleep(0.1) if show_progress: cnt += 1 if cnt % 10 == 0: if not output_started: output_started = 1 sys.stderr.write("waiting for %s to finish " % what) sys.stderr.write(".") 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: