Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F2825423
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
99 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/tools/crm.in b/tools/crm.in
index 447d35f1f8..fe9796763c 100644
--- a/tools/crm.in
+++ b/tools/crm.in
@@ -1,3405 +1,3448 @@
#!/usr/bin/env python
#
# Copyright (C) 2008 Dejan Muhamedagic <dmuhamedagic@suse.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import shlex
import os
from tempfile import mkstemp
from popen2 import Popen3
import sys
import readline
import copy
import xml.dom.minidom
class ErrorBuffer(object):
'''
Show error messages either immediately or buffered.
'''
def __init__(self):
self.msg_list = []
self.mode = "immediate"
def buffer(self):
self.mode = "keep"
def release(self):
if self.msg_list:
print '\n'.join(self.msg_list)
if interactive:
raw_input("Press enter to continue... ")
self.msg_list = []
self.mode = "immediate"
def writemsg(self,msg):
if self.mode == "immediate":
print msg
else:
self.msg_list.append(msg)
def error(self,s):
self.writemsg("ERROR: %s" % s)
def warning(self,s):
self.writemsg("WARNING: %s" % s)
def info(self,s):
self.writemsg(s)
def common_err(s):
err_buf.error(s)
def common_warn(s):
err_buf.warning(s)
def common_info(s):
err_buf.info(s)
def no_prog_err(name):
err_buf.error("%s not available, check your installation"%name)
def missing_prog_warn(name):
err_buf.warning("could not find any %s on the system"%name)
def no_attribute_err(attr):
err_buf.error("required attribute %s not found in %s"%(attr,obj_type))
def unsupported_err(name):
err_buf.error("%s is not supported"%name)
def no_such_obj_err(name):
err_buf.error("%s object is not supported"%name)
def obj_cli_err(name):
err_buf.error("object %s cannot be represented in the CLI notation"%name)
def missing_obj_err(node):
err_buf("object %s:%s missing (shouldn't have happened)"% \
(node.tagName,node.getAttribute("id")))
def obj_exists_err(name):
err_buf.error("object %s already exists"%name)
def no_object_err(name):
err_buf.error("object %s does not exist"%name)
def id_used_err(node_id):
err_buf.error("%s id is already in use"%node_id)
def skill_err(s):
err_buf.error("%s: this command is not allowed at this skill level"%' '.join(s))
def syntax_err(s,token = ''):
if type(s) == type(''):
err_buf.error("syntax error near <%s>"%s)
elif token:
err_buf.error("syntax error near <%s>: %s"%(token,' '.join(s)))
else:
err_buf.error("syntax error: %s"%' '.join(s))
def bad_attr_usage(cmd,args):
err_buf.error("bad usage: %s %s"%(cmd,args))
def info_msg(msg):
err_buf.info(msg)
def cib_parse_err(msg):
err_buf.error("%s"%msg)
def cib_ver_unsupported_err(cib_rel):
err_buf.error("CIB release '%s' is not supported"% cib_rel)
def update_err(obj_id,cibadm_opt,node):
if cibadm_opt == '-C':
task = "create"
elif cibadm_opt == '-D':
task = "delete"
else:
task = "update"
err_buf.error("could not %s %s"%(task,obj_id))
err_buf.info("offending xml: %s" % node.toprettyxml())
def not_impl_info(s):
err_buf.info("%s is not implemented yet" % s)
def ask(msg):
ans = raw_input(msg + ' ')
if not ans:
return False
return ans[0].lower() == 'y'
def cmd_end(cmd):
"Go up one level."
levels.droplevel()
def cmd_exit(cmd):
"Quit"
global interactive
if interactive:
print "bye"
try:
readline.write_history_file(hist_file)
except:
pass
sys.exit()
def dump_short_desc(help_tab):
for topic in help_tab:
if topic == '.':
continue
print "\t",topic.ljust(16),help_tab[topic][0]
def cmd_help(help_tab,topic = ''):
"help!"
# help_tab is a dict: topic: (short_desc,long_desc)
# '.' is a special entry for the top level
if not topic:
print ""
print help_tab['.'][1]
print ""
print "Available commands:"
print ""
dump_short_desc(help_tab)
print ""
return
if topic not in help_tab:
print "There is no help for topic %s" % topic
return
if not help_tab[topic][1]:
print help_tab[topic][0]
else:
print help_tab[topic][1]
def pipe_string(cmd,s):
if user_prefs.crm_user:
p = os.popen("sudo -E -u %s %s"%(user_prefs.crm_user,cmd),'w')
else:
p = os.popen(cmd,'w')
p.write(s)
return p.close()
#def pipe_string(cmd,s):
# 'Run a program, collect and return stdout.'
# if user_prefs.crm_user:
# p = Popen3("sudo -E -u %s %s"%(user_prefs.crm_user,cmd), None)
# else:
# p = Popen3(cmd, None)
# p.fromchild.close()
# p.tochild.write(s)
# p.tochild.close()
# p.wait()
# Stolen from crm_utils.py
def os_system(cmd, print_raw=False):
'Run a program, collect and return stdout.'
if user_prefs.crm_user:
p = Popen3("sudo -E -u %s %s"%(user_prefs.crm_user,cmd), None)
else:
p = Popen3(cmd, None)
p.tochild.close()
result = p.fromchild.readlines()
p.fromchild.close()
p.wait()
if print_raw:
for line in result:
print line.rstrip()
return result
def ext_cmd(s):
if user_prefs.crm_user:
return os.system("sudo -E -u %s %s"%(user_prefs.crm_user,s))
else:
return os.system(s)
def is_program(prog):
return os.system("which %s >/dev/null 2>&1"%prog) == 0
def find_program(envvar,*args):
if os.getenv(envvar):
return os.getenv(envvar)
for prog in args:
if is_program(prog):
return prog
class UserPrefs(object):
'''
Keep user preferences here.
'''
def __init__(self):
self.skill_level = 2 #TODO: set back to 0?
self.editor = find_program("EDITOR","vim","vi","emacs","nano")
self.pager = find_program("PAGER","less","more","pg")
if not self.editor:
missing_prog_warn("editor")
if not self.pager:
missing_prog_warn("pager")
self.crm_user = ""
self.xmlindent = " " # two spaces
def check_skill_level(self,n):
return self.skill_level >= n
class CliOptions(object):
'''
Manage user preferences
'''
skill_levels = {"operator":0, "administrator":1, "expert":2}
help_table = {
".": ("user preferences","Various user preferences may be set here."),
"skill-level": ("set skill level", ""),
"editor": ("set prefered editor program", ""),
"pager": ("set prefered pager program", ""),
"user": ("set the cluster user", """
If you need extra privileges to talk to the cluster (i.e. the cib
process), then set this to user. Typically, that is either "root"
or "hacluster". Don't forget to setup the sudoers file as well.
Example: user hacluster
"""),
"help": ("show help", ""),
"end": ("go back one level", ""),
}
def __init__(self):
self.cmd_table = {
"skill-level": (self.set_skill_level,(1,1),0,(skills_list,)),
"editor": (self.set_editor,(1,1),0),
"pager": (self.set_pager,(1,1),0),
"user": (self.set_crm_user,(0,1),0),
"save": (self.save_options,(0,0),0),
"show": (self.show_options,(0,0),0),
"help": (self.help,(0,1),0),
"quit": (cmd_exit,(0,0),0),
"end": (cmd_end,(0,0),0),
}
def set_skill_level(self,cmd,skill_level):
"""usage: skill-level <level>
level: operator | administrator | expert"""
global user_prefs
if skill_level in self.skill_levels:
user_prefs.skill_level = self.skill_levels[skill_level]
else:
common_err("no %s skill level"%skill_level)
def get_skill_level(self):
for s in self.skill_levels:
if user_prefs.skill_level == self.skill_levels[s]:
return s
def set_editor(self,cmd,prog):
"usage: editor <program>"
global user_prefs
if is_program(prog):
user_prefs.editor = prog
else:
common_err("program %s does not exist"% prog)
def set_pager(self,cmd,prog):
"usage: pager <program>"
global user_prefs
if is_program(prog):
user_prefs.pager = prog
else:
common_err("program %s does not exist"% prog)
def set_crm_user(self,cmd,user = ''):
"usage: user [<crm_user>]"
global user_prefs
user_prefs.crm_user = user
def write_rc(self,f):
print >>f, '%s "%s"' % ("editor",user_prefs.editor)
print >>f, '%s "%s"' % ("pager",user_prefs.pager)
print >>f, '%s "%s"' % ("user",user_prefs.crm_user)
print >>f, '%s "%s"' % ("skill-level",self.get_skill_level())
def show_options(self,cmd):
"usage: show"
self.write_rc(sys.stdout)
def save_options(self,cmd):
"usage: save"
global rc_file
global user_prefs
try: f = open(rc_file,"w")
except os.error,msg:
common_err("open: %s"%msg)
return
print >>f, 'options'
self.write_rc(f)
print >>f, 'end'
f.close()
def help(self,cmd,topic = ''):
"usage: help [<topic>]"
global user_prefs
cmd_help(self.help_table,topic)
cib_dump = "cibadmin -Ql"
cib_piped = "cibadmin -p"
cib_verify = "crm_verify -V -p"
-def attr_cmds():
- return ["delete","set","show"]
-def rsc_list():
- cmd = "%s -o %s" % (cib_dump,"resources")
- s = '\n'.join(os_system(cmd))
- try: doc = xml.dom.minidom.parseString(s)
- except xml.parsers.expat.ExpatError,msg:
- cib_parse_err(msg)
- return []
- nodes = get_interesting_nodes(doc,[])
- return [x.getAttribute("id") for x in nodes if is_resource(x)]
-def skills_list():
- return CliOptions.skill_levels.keys()
+class WCache(object):
+ "Cache stuff. A naive implementation."
+ def __init__(self):
+ self.lists = {}
+ def is_cached(self,name):
+ return name in self.lists
+ def cache(self,name,list):
+ self.lists[name] = list
+ return list
+ def cached(self,name):
+ if self.is_cached(name):
+ return self.lists[name]
+ else:
+ return None
+ def clear_cache(self):
+ self.lists = {}
class CibShadow(object):
'''
CIB shadow management class
'''
help_table = {
".": ("","""
CIB shadow management.
See the crm_shadow program.
"""),
"new": ("create a new shadow CIB", ""),
"delete": ("delete a shadow CIB", ""),
"reset": ("copy live cib to a shadow CIB", ""),
"commit": ("copy a shadow CIB to the cluster", ""),
"use": ("change working CIB", '''
Choose a shadow CIB for further changes. If the name
provided is empty, then the live (cluster) CIB is used.
'''),
"diff": ("diff between the shadow CIB and the live CIB", ""),
"list": ("list all shadow CIBs", ""),
"quit": ("exit the program", ""),
"help": ("show help", ""),
"end": ("go back one level", ""),
}
envvar = "CIB_shadow"
extcmd = ">/dev/null </dev/null crm_shadow"
def __init__(self):
self.cmd_table = {
"new": (self.new,(1,1),1),
"delete": (self.delete,(1,1),1),
"reset": (self.reset,(1,1),1),
"commit": (self.commit,(1,1),1),
"use": (self.use,(0,1),1),
"diff": (self.diff,(0,0),1),
"list": (self.list,(0,0),1),
"help": (self.help,(0,1),0),
"quit": (cmd_exit,(0,0),0),
"end": (cmd_end,(0,0),0),
}
self.chkcmd()
def chkcmd(self):
try:
os.system("%s 2>&1" % self.extcmd)
except os.error:
no_prog_err(self.extcmd)
return False
return True
def new(self,cmd,name):
"usage: create <shadow_cib>"
if ext_cmd("%s -c %s" % (self.extcmd,name)) == 0:
info_msg("%s shadow CIB created"%name)
else:
common_err("failed to create %s shadow CIB"%name)
def delete(self,cmd,name):
"usage: delete <shadow_cib>"
if ext_cmd("%s -D %s --force" % (self.extcmd,name)) == 0:
info_msg("%s shadow CIB deleted"%name)
else:
common_err("failed to delete %s shadow CIB"%name)
def reset(self,cmd,name):
"usage: reset <shadow_cib>"
if ext_cmd("%s -r %s" % (self.extcmd,name)) == 0:
info_msg("copied live CIB to %s"%name)
else:
common_err("failed to copy live CIB to %s"%name)
def commit(self,cmd,name):
"usage: commit <shadow_cib>"
if ext_cmd("%s -C %s --force" % (self.extcmd,name)) == 0:
info_msg("commited %s shadow CIB to the cluster"%name)
else:
common_err("failed to commit the %s shadow CIB"%name)
def diff(self,cmd):
"usage: diff"
ext_cmd("%s -d" % self.extcmd)
def list(self,cmd):
"usage: list"
ext_cmd("ls @HA_VARLIBDIR@/heartbeat/crm | fgrep shadow.")
def use(self,cmd,name = ''):
"usage: use [<shadow_cib>]"
# Choose a shadow cib for further changes. If the name
# provided is empty, then choose the live (cluster) cib.
global cib_in_use
if name:
if ext_cmd("test -r @HA_VARLIBDIR@/heartbeat/crm/shadow.%s"%name) != 0:
common_err("%s: no such shadow CIB"%name)
return
os.putenv(self.envvar,name)
else:
os.unsetenv(self.envvar)
cib_in_use = name
def help(self,cmd,topic = ''):
cmd_help(self.help_table,topic)
def manage_attr(cmd,attr_ext_commands,*args):
if len(args) < 3:
bad_attr_usage(cmd,' '.join(args))
attr_cmd = attr_ext_commands[args[1]]
if not attr_cmd:
bad_attr_usage(cmd,' '.join(args))
if args[1] == 'set':
if len(args) == 4:
ext_cmd(attr_cmd%(args[0],args[2],args[3]))
else:
bad_attr_usage(cmd,' '.join(args))
elif args[1] in ('delete','show'):
if len(args) == 3:
ext_cmd(attr_cmd%(args[0],args[2]))
else:
bad_attr_usage(cmd,' '.join(args))
else:
bad_attr_usage(cmd,' '.join(args))
class RscMgmt(object):
'''
Resources management class
'''
rsc_status_all = "crm_resource -L"
rsc_status = "crm_resource -W -r %s"
rsc_startstop = "crm_resource --meta -r %s -p target_role -v %s"
rsc_manage = "crm_resource --meta -r %s -p is_managed -v %s"
rsc_migrate = "crm_resource -M -r %s"
rsc_migrateto = "crm_resource -M -r %s -H %s"
rsc_unmigrate = "crm_resource -U -r %s"
rsc_cleanup = "crm_resource -C -r %s -H %s"
rsc_param = {
'set': "crm_resource -r %s -p %s -v %s",
'delete': "crm_resource -r %s -d %s",
'show': "crm_resource -r %s -g %s",
}
rsc_meta = {
'set': "crm_resource --meta -r %s -p %s -v %s",
'delete': "crm_resource --meta -r %s -d %s",
'show': "crm_resource --meta -r %s -g %s",
}
rsc_failcount = {
'set': "crm_failcount -r %s -U %s -v %s",
'delete': "crm_failcount -r %s -U %s -D",
'show': "crm_failcount -r %s -U %s -G",
}
rsc_refresh = "crm_resource -R"
rsc_refresh_node = "crm_resource -R -H %s"
rsc_reprobe = "crm_resource -P"
rsc_reprobe_node = "crm_resource -P -H %s"
help_table = {
".": ("","Resource management."),
"status": ("show status of resources", ""),
"start": ("start a resource", ""),
"stop": ("stop a resource", ""),
"manage": ("put a resource into managed mode", ""),
"unmanage": ("put a resource into unmanaged mode", ""),
"migrate": ("migrate a resource to another node", ""),
"unmigrate": ("migrate a resource to its prefered node", ""),
"param": ("manage a parameter of a resource","""
Manage or display a parameter of a resource (also known as an
instance_attribute).
Usage:
param <rsc> set <param> <value>
param <rsc> delete <param>
param <rsc> show <param>
Example:
param ip_0 show ip
"""),
"meta": ("manage a meta attribute","""
Show/edit/delete a meta attribute of a resource. Currently, all
meta attributes of a resource may be managed with other commands
such as 'resource stop'.
Usage:
meta <rsc> set <attr> <value>
meta <rsc> delete <attr>
meta <rsc> show <attr>
Example:
meta ip_0 set target_role stopped
"""),
"failcount": ("manage failcounts", """
Show/edit/delete the failcount of a resource.
Usage:
failcount <rsc> set <node> <value>
failcount <rsc> delete <node>
failcount <rsc> show <node>
Example:
failcount fs_0 delete node2
"""),
"cleanup": ("cleanup resource status",""),
"refresh": ("refresh CIB from the LRM status",""),
"reprobe": ("probe for resources not started by the CRM",""),
"quit": ("exit the program", ""),
"help": ("show help", ""),
"end": ("go back one level", ""),
}
def __init__(self):
self.cmd_table = {
"status": (self.status,(0,1),0,(rsc_list,)),
"start": (self.start,(1,1),0,(rsc_list,)),
"stop": (self.stop,(1,1),0,(rsc_list,)),
"manage": (self.manage,(1,1),0,(rsc_list,)),
"unmanage": (self.unmanage,(1,1),0,(rsc_list,)),
"migrate": (self.migrate,(1,2),0,(rsc_list,listnodes)),
"unmigrate": (self.unmigrate,(1,1),0,(rsc_list,)),
"param": (self.param,(3,4),1,(rsc_list,attr_cmds)),
"meta": (self.meta,(3,4),1,(rsc_list,attr_cmds)),
"failcount": (self.failcount,(3,4),0,(rsc_list,attr_cmds,listnodes)),
"cleanup": (self.cleanup,(1,2),1,(rsc_list,listnodes)),
"refresh": (self.refresh,(0,1),0,(listnodes,)),
"reprobe": (self.reprobe,(0,1),0,(listnodes,)),
"help": (self.help,(0,1),0),
"quit": (cmd_exit,(0,0),0),
"end": (cmd_end,(0,0),0),
}
def status(self,cmd,rsc = None):
"usage: status [<rsc>]"
if rsc:
ext_cmd(self.rsc_status % rsc)
else:
ext_cmd(self.rsc_status_all)
def start(self,cmd,rsc):
"usage: start <rsc>"
ext_cmd(self.rsc_startstop%(rsc,"Started"))
def stop(self,cmd,rsc):
"usage: stop <rsc>"
ext_cmd(self.rsc_startstop%(rsc,"Stopped"))
def manage(self,cmd,rsc):
"usage: manage <rsc>"
ext_cmd(self.rsc_manage%(rsc,"true"))
def unmanage(self,cmd,rsc):
"usage: unmanage <rsc>"
ext_cmd(self.rsc_manage%(rsc,"false"))
def migrate(self,cmd,*args):
"""usage: migrate <rsc> [<node>]"""
if len(args) == 1:
ext_cmd(self.rsc_migrate%args[0])
else:
ext_cmd(self.rsc_migrateto%(args[0],args[1]))
def unmigrate(self,cmd,rsc):
"usage: unmigrate <rsc>"
ext_cmd(self.rsc_unmigrate%rsc)
def cleanup(self,cmd,*args):
"usage: cleanup <node>"
# Cleanup a resource on a node. Omit node to cleanup on
# all live nodes.
if len(args) == 2: # remove
ext_cmd(self.rsc_cleanup%(args[0],args[1]))
else:
for n in listnodes():
ext_cmd(self.rsc_cleanup%(args[0],n))
def failcount(self,cmd,*args):
"""usage:
failcount <rsc> set <node> <value>
failcount <rsc> delete <node>
failcount <rsc> show <node>"""
d = lambda: manage_attr(cmd,self.rsc_failcount,*args)
d()
def param(self,cmd,*args):
"""usage:
param <rsc> set <param> <value>
param <rsc> delete <param>
param <rsc> show <param>"""
d = lambda: manage_attr(cmd,self.rsc_param,*args)
d()
def meta(self,cmd,*args):
"""usage:
meta <rsc> set <attr> <value>
meta <rsc> delete <attr>
meta <rsc> show <attr>"""
d = lambda: manage_attr(cmd,self.rsc_meta,*args)
d()
def refresh(self,cmd,*args):
'usage: refresh [<node>]'
if len(args) == 1:
ext_cmd(self.rsc_refresh_node%args[0])
else:
ext_cmd(self.rsc_refresh)
def reprobe(self,cmd,*args):
'usage: reprobe [<node>]'
if len(args) == 1:
ext_cmd(self.rsc_reprobe_node%args[0])
else:
ext_cmd(self.rsc_reprobe)
def help(self,cmd,topic = ''):
cmd_help(self.help_table,topic)
all_nodes = "crmadmin -N"
def listnodes():
nodes = []
for l in os_system(all_nodes):
s = l.split()
if s[0] == 'normal':
nodes.append(s[2])
return nodes
class NodeMgmt(object):
'''
Nodes management class
'''
node_standby = "crm_standby -U %s -v %s"
cib_dump_nodes = "cibadmin -Q -o nodes"
dc = "crmadmin -D"
node_status = "crmadmin -S %s"
node_attr = {
'set': "crm_attribute -t nodes -U %s -n %s -v %s",
'delete': "crm_attribute -D -t nodes -U %s -n %s",
'show': "crm_attribute -G -t nodes -U %s -n %s",
}
node_status = {
'set': "crm_attribute -t status -U %s -n %s -v %s",
'delete': "crm_attribute -D -t status -U %s -n %s",
'show': "crm_attribute -G -t status -U %s -n %s",
}
help_table = {
".": ("","Nodes management."),
"status": ("show status", ""),
"show": ("show node", ""),
"standby": ("put node into standby", ""),
"online": ("bring node online", ""),
"attribute": ("manage attributes", """
Edit node attributes. This kind of attribute should refer to
relatively static properties, such as memory size.
Usage:
attribute <node> set <attr> <value>
attribute <node> delete <attr>
attribute <node> show <attr>
Example:
attribute node_1 set memory_size 4096
"""),
"status-attr": ("manage status attributes", """
Edit node attributes which are in the CIB status section, i.e.
attributes which hold properties of a more volatile nature. One
typical example is attribute generated by the 'pingd' utility.
Usage:
...............
status-attr <node> set <attr> <value>
status-attr <node> delete <attr>
status-attr <node> show <attr>
...............
Example:
...............
status-attr node_1 show pingd
"""),
"quit": ("exit the program", ""),
"help": ("show help", ""),
"end": ("go back one level", ""),
}
def __init__(self):
self.cmd_table = {
"status": (self.status,(0,1),0,(listnodes,)),
"show": (self.show,(0,1),0,(listnodes,)),
"standby": (self.standby,(1,1),0,(listnodes,)),
"online": (self.online,(1,1),0,(listnodes,)),
"attribute": (self.attribute,(3,4),0,(listnodes,attr_cmds)),
"status-attr": (self.status_attr,(3,4),0,(listnodes,attr_cmds)),
"help": (self.help,(0,1),0),
"quit": (cmd_exit,(0,0),0),
"end": (cmd_end,(0,0),0),
}
def status(self,cmd,node = None):
'usage: status [<node>]'
ext_cmd(self.cib_dump_nodes)
def show(self,cmd,node = None):
'usage: show [<node>]'
ext_cmd(self.cib_dump_nodes)
def standby(self,cmd,node):
'usage: standby <node>'
ext_cmd(self.node_standby%(node,"on"))
def online(self,cmd,node):
'usage: online <node>'
ext_cmd(self.node_standby%(node,"off"))
def attribute(self,cmd,*args):
"""usage:
attribute <rsc> set <node> <value>
attribute <rsc> delete <node>
attribute <rsc> show <node>"""
d = lambda: manage_attr(cmd,self.node_attr,*args)
d()
def status_attr(self,cmd,*args):
"""usage:
status-attr <rsc> set <node> <value>
status-attr <rsc> delete <node>
status-attr <rsc> show <node>"""
d = lambda: manage_attr(cmd,self.node_status,*args)
d()
def help(self,cmd,topic = ''):
cmd_help(self.help_table,topic)
def edit_file(fname):
'Edit a file.'
global user_prefs
if not fname:
return
if not user_prefs.editor:
return
return os.system("%s %s" % (user_prefs.editor,fname))
def page_string(s):
'Write string through a pager.'
global user_prefs
if not s:
return
if not user_prefs.pager or not interactive:
print s
else:
pipe_string(user_prefs.pager,s)
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]
class CibConfig(object):
'''
The configuration class
'''
help_table = {
".": ("","CIB configuration."),
"verify": ("verify the CIB before commit", "Verify the CIB (before commit)"),
"erase": ("erase the CIB", "Erase the CIB (careful!)."),
"show": ("display CIB objects", """
The `show` command displays objects. It may display all objects
or a set of objects. Specify 'changed' to see what changed.
Usage:
...............
show [xml] [<id> ...]
show [xml] changed
...............
"""),
"edit": ("edit CIB objects", """
This command invokes the editor with the object description. As
with the `show` command, the user may choose to edit all objects
or a set of objects.
If the user insists, he or she may edit the XML edition of the
object.
Usage:
...............
edit [xml] [<id> ...]
edit [xml] changed
...............
"""),
"delete": ("delete CIB objects", """
The user may delete one or more objects by specifying a list of
ids. If the object to be deleted belongs to a container object,
such as group, and it is the only resource in that container,
then the container is deleted as well.
Usage:
...............
delete <id> [<id> ...]
...............
"""),
"save": ("save the CIB to a file", """
Save the configuration to a file. Optionally, as XML.
Usage:
...............
save [xml] <file>
...............
Example:
...............
save myfirstcib.txt
...............
"""),
"load": ("import the CIB from a file", """
Load a part of configuration (or all of it) from a local file or
a network URL. The various methods of importing refer to the
`cibadmin` and its `-C`, `-R`, and `-U` options. The file may be
a CLI file or an XML file.
Usage:
...............
load [xml] method URL
method :: replace | update
...............
Example:
...............
load xml replace myfirstcib.xml
load xml replace http://storage.big.com/cibs/bigcib.xml
...............
"""),
"template": ("edit and import a configuration from a template", """
The specified template is loaded into the editor. It's up to the
user to make a good CRM configuration out of it.
Usage:
...............
template [xml] url
...............
Example:
...............
template two-apaches.txt
...............
"""),
"enter": ("enter the interactive configuration mode", """
Those who prefer more typing, or in case the definition of a
resource is rather long, the 'enter' command offers configuration
in small steps.
Usage:
...............
enter <obj_type> <id>
...............
Example:
...............
enter primitive ip_1
...............
"""),
"commit": ("commit the changes to the CIB", """
The changes at the configure level are not immediately applied to
the CIB, but by this command or on exiting the configure level.
Sometimes, the program will refuse to apply the changes, usually
for good reason. If you know what you're doing, you may say
'commit force' to force the changes.
"""),
"primitive": ("define a resource", """
The primitive command describes a resource.
Usage:
...............
primitive <rsc> [<class>:[<provider>:]]<type>
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]
[operations id_spec
[op op_type [<attribute>=<value>...] ...]]
id_spec :: $id=<id> | $id-ref=<id>
op_type :: start | stop | monitor
...............
Example:
...............
primitive apcfence stonith:apcsmart \
params ttydev=/dev/ttyS0 hostlist="node1 node2" \
op start timeout=60s \
op monitor interval=30m timeout=60s
primitive www8 apache \
params configfile=/etc/apache/www8.conf \
operations $id-ref=apache_ops
...............
"""),
"group": ("define a group", """
The `group` command creates a group of resources.
Usage:
...............
group <name> <rsc> [<rsc>...]
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]
...............
Example:
...............
group internal_www disk0 fs0 internal_ip apache \
meta target_role=stopped
...............
"""),
"clone": ("define a clone", """
The `clone` command creates a resource clone. It may contain a
single primitive resource or one group of resources.
Usage:
...............
clone <name> <rsc>
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]
...............
Example:
...............
clone cl_fence apc_1 \
meta clone_node_max=1 globally_unique=false
...............
"""),
"ms": ("define a master-slave resource", """
The `ms` command creates a master/slave resource type. It may contain a
single primitive resource or one group of resources.
Usage:
...............
ms <name> <rsc>
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]
...............
Example:
...............
ms disk1 drbd1 \
meta notify=true globally_unique=false
...............
"""),
"location": ("a location preference", """
`location` defines the preference of nodes for the given
resource. The location constraints consist of one or more rules
which specify a score to be awarded if the rule matches.
Usage:
...............
location <id> <rsc>
rule [id_spec] [$role=<role>] <score> <expression>
[rule [id_spec] [$role=<role>] <score> <expression> ...]
id_spec :: $id=<id> | $id-ref=<id>
score :: <number> | <attribute> | [-]inf
expression :: <single_exp> [bool_op <simple_exp> ...]
| <date_expr>
bool_op :: or | and
single_exp :: <attribute> [type:]<binary_op> <value>
| <unary_op> <attribute>
type :: string | version | number
binary_op :: lt | gt | lte | gte | eq | ne
unary_op :: defined | not_defined
date_expr :: date_op <start> [<end>] (TBD)
...............
Examples:
...............
location conn_1 internal_www \
rule 50 uname eq node1 \
rule pingd defined pingd
location conn_2 dummy_float \
rule -inf not_defined pingd or pingd lte 0
...............
"""),
"colocation": ("colocate resources", """
This constraint expresses the placement relation between two
resources.
Usage:
...............
colocation <id> <score> <rsc>[:<role>] <rsc>[:<role>]
[symmetrical=<bool>]
...............
Example:
...............
colocation dummy_and_apache -inf apache dummy
...............
"""),
"order": ("order resources", """
This constraint expresses the order of actions on two resources.
Usage:
...............
order <id> score-type <first-rsc>[:<action>] <then-rsc>[:<action>]
[symmetrical=<bool>]
score-type :: advisory | mandatory | <score>
...............
Example:
...............
order c_apache_1 mandatory apache:start ip_1
...............
"""),
"property": ("set a cluster property", """
Set the cluster (`crm_config`) options.
Usage:
...............
property [$id=<set_id>]
<option>=<value> [<option>=<value>...]
...............
Example:
...............
property stonith-enabled=true
...............
"""),
"help": ("show help", ""),
"end": ("go back one level", ""),
"exit": ("exit program", ""),
}
def __init__(self):
self.cmd_table = {
"erase": (self.erase,(0,0),1),
"verify": (self.verify,(0,0),1),
"commit": (self.commit,(0,1),1),
"show": (self.show,(0,),1),
"edit": (self.edit,(0,),1),
"delete": (self.delete,(1,),1),
"save": (self.save,(1,2),1),
"load": (self.load,(2,3),1),
"template": (self.template,(1,2),1),
"enter": (self.enter,(2,2),1),
"primitive": (self.conf_primitive,(2,),1),
"group": (self.conf_group,(2,),1),
"clone": (self.conf_clone,(2,),1),
"ms": (self.conf_ms,(2,),1),
"location": (self.conf_location,(2,),1),
"colocation": (self.conf_colocation,(2,),1),
"order": (self.conf_order,(2,),1),
- "property": (self.conf_property,(1,),1),
+ "property": (self.conf_property,(1,),1,(property_list,)),
"help": (self.help,(0,1),1),
"end": (self.end,(0,0),1),
"exit": (self.exit,(0,0),1),
"test": (self.check_structure,(0,0),1),
}
- cib_factory.initialize()
+ if not build_completions:
+ cib_factory.initialize()
def check_structure(self,cmd):
global cib_factory
cib_factory.check_structure()
def _mkset_obj(self,*args):
if args and args[0] == "xml":
obj = lambda: CibObjectSetRaw(*args[1:])
else:
obj = lambda: CibObjectSetCli(*args)
return obj()
def show(self,cmd,*args):
"usage: show [xml] [<id>...]"
set_obj = self._mkset_obj(*args)
set_obj.show()
def edit(self,cmd,*args):
"usage: edit [xml] [<id>...]"
set_obj = self._mkset_obj(*args)
set_obj.edit()
def verify(self,cmd):
"usage: verify"
set_obj = self._mkset_obj("xml","NOOBJ")
set_obj.verify()
def save(self,cmd,*args):
"usage: save [xml] <filename>"
if args[0] == "xml":
file = args[1]
set_obj = self._mkset_obj("xml")
else:
file = args[0]
set_obj = self._mkset_obj()
set_obj.save_to_file(file)
def load(self,cmd,*args):
"usage: load [xml] {replace|update} {<url>|<path>}"
if args[0] == "xml":
url = args[2]
method = args[1]
set_obj = self._mkset_obj("xml","NOOBJ")
else:
url = args[1]
method = args[0]
set_obj = self._mkset_obj("NOOBJ")
set_obj.import_file(method,url)
def template(self,cmd,*args):
"usage: template [xml] {<url>|<path>}"
if batch:
common_info("template not allowed in batch mode")
return
if args[0] == "xml":
url = args[1]
set_obj = self._mkset_obj("xml","NOOBJ")
else:
url = args[0]
set_obj = self._mkset_obj("NOOBJ")
set_obj.import_template(url)
def delete(self,cmd,*args):
"usage: delete <id> [<id>...]"
global cib_factory
cib_factory.delete(*args)
def erase(self,cmd):
"usage: erase"
global cib_factory
cib_factory.erase()
def commit(self,cmd,force = None):
"usage: commit [force]"
global cib_factory
if not force:
cib_factory.commit()
elif force == "force":
cib_factory.commit(True)
else:
syntax_err((cmd,force))
return
def enter(self,cmd,obj_type,obj_id):
"usage: enter obj_type id"
not_impl_info("enter")
return
global cib_factory
if not obj_type in self.interactive:
no_such_obj_err(obj_type)
return False
obj = cib_factory.find_object(obj_id) or \
cib_factory.new_object(obj_type,obj_id)
if not obj:
return
if obj.nocli:
obj_cli_err(obj_id)
return
self.int_inst = self.interactive[obj_type](obj)
ptab = {
"end": (self.int_inst.end,())
}
ptab.update(self.int_inst.cmd_table)
return ptab
def conf_object(self,cmd,*args):
"The configure object command."
f = lambda: cib_factory.create_object(cmd,*args)
f()
def conf_primitive(self,cmd,*args):
"""usage: primitive <rsc> [<class>:[<provider>:]]<type>
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]
[operations id_spec
[op op_type [<attribute>=<value>...] ...]]"""
self.conf_object(cmd,*args)
def conf_group(self,cmd,*args):
"""usage: group <name> <rsc> [<rsc>...]
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]"""
self.conf_object(cmd,*args)
def conf_clone(self,cmd,*args):
"""usage: clone <name> <rsc>
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]"""
self.conf_object(cmd,*args)
def conf_ms(self,cmd,*args):
"""usage: ms <name> <rsc>
[params <param>=<value> [<param>=<value>...]]
[meta <attribute>=<value> [<attribute>=<value>...]]"""
self.conf_object(cmd,*args)
def conf_location(self,cmd,*args):
"""usage: location <id> <rsc>
rule [id_spec] [$role=<role>] <score> <expression>
[rule [id_spec] [$role=<role>] <score> <expression> ...]"""
self.conf_object(cmd,*args)
def conf_colocation(self,cmd,*args):
"""usage: colocation <id> <score> <rsc>[:<role>] <rsc>[:<role>]
[symmetrical=<bool>]"""
self.conf_object(cmd,*args)
def conf_order(self,cmd,*args):
"""usage: order <id> score-type <first-rsc>[:<action>] <then-rsc>[:<action>]
[symmetrical=<bool>]"""
self.conf_object(cmd,*args)
def conf_property(self,cmd,*args):
"usage: property [$id=<set_id>] <option>=<value>"
self.conf_object(cmd,*args)
def end_game(self):
global cib_factory
global interactive
if cib_factory.has_cib_changed():
if not interactive or \
ask("There are changes pending. Do you want to commit them?"):
cib_factory.commit()
cib_factory.reset()
def end(self,cmd):
"usage: end"
self.end_game()
cmd_end(cmd)
def help(self,cmd,topic = ''):
"usage: help [<topic>]"
cmd_help(self.help_table,topic)
def exit(self,cmd):
"usage: exit"
self.end_game()
cmd_exit(cmd)
def is_element(xmlnode):
return 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,filter,proc):
'''
Process with proc all nodes that match filter.
'''
node_list = []
for child in xmlnode.childNodes:
if filter(child):
node_list.append(child)
elif child.hasChildNodes():
xml_processnodes(child,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_status_node(node):
return is_element(node) and node.tagName == "status"
container_tags = ("group", "clone", "ms", "master")
resource_tags = ("primitive","group","clone","ms","master")
constraint_tags = ("rsc_location","rsc_colocation","rsc_order")
constraint_rsc_refs = ("rsc","with-rsc","first-rsc","then-rsc")
children_tags = ("group", "primitive")
nvpairs_tags = ("rsc_location", "meta_attributes", "instance_attributes")
def is_emptynvpairs(node):
return is_element(node) \
and node.tagName in nvpairs_tags \
and not node.hasChildNodes()
def is_container(node):
return is_element(node) \
and node.tagName in container_tags
def is_resource(node):
return is_element(node) \
and node.tagName in resource_tags
def is_constraint(node):
return is_element(node) \
and node.tagName in constraint_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 constraint_rsc_refs \
and rsc_id == cons_node.getAttribute(attr):
return True
return False
resource_cli_names = ("primitive","group","clone","ms")
constraint_cli_names = ("location","colocation","order")
def is_resource_cli(s):
return s in resource_cli_names
def is_constraint_cli(s):
return s in constraint_cli_names
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 children_tags:
children.append(c)
for c in children:
node.removeChild(c)
for c in children:
node.appendChild(c)
def rmnodes(node_list):
for node in node_list:
node.parentNode.removeChild(node)
node.unlink()
def sanitize_cib(doc):
xml_processnodes(doc,is_status_node,rmnodes)
xml_processnodes(doc,is_emptynvpairs,rmnodes)
xml_processnodes(doc,is_whitespace,rmnodes)
xml_processnodes(doc,is_container,sort_container_children)
class IdMgmt(object):
'''
Make sure that ids are unique.
'''
def __init__(self):
self._id_store = {}
self.ok = True # error var
def new(self,node,pfx):
'''
Create a unique id for the xml node.
'''
global hints_list
name = node.getAttribute("name")
if node.tagName == "nvpair":
node_id = "%s-%s" % (pfx,name)
elif node.tagName == "op":
interval = node.getAttribute("interval")
if interval:
node_id = "%s-%s-%s" % (pfx,name,interval)
else:
node_id = "%s-%s" % (pfx,name)
else:
try:
hint = hints_list[node.tagName]
except: pass
if hint:
node_id = "%s-%s" % (pfx,hint)
else:
node_id = "%s" % pfx
if self.is_used(node_id):
for cnt in range(99): # shouldn't really get here
try_id = "%s-%d" % (node_id,cnt)
if not self.is_used(try_id):
node_id = try_id
break
self.save(node_id)
return node_id
def check_node(self,node,lvl):
node_id = node.getAttribute("id")
if not node_id:
return
if self.is_used(node_id):
id_used_err(node_id)
self.ok = False
return
def _store_node(self,node,lvl):
self.save(node.getAttribute("id"))
def _drop_node(self,node,lvl):
self.remove(node.getAttribute("id"))
def check_xml(self,node):
self.ok = True
xmltraverse_thin(node,self.check_node)
return self.ok
def store_xml(self,node):
if not self.check_xml(node):
return False
xmltraverse_thin(node,self._store_node)
return True
def remove_xml(self,node):
xmltraverse_thin(node,self._drop_node)
def replace_xml(self,oldnode,newnode):
self.remove_xml(oldnode)
if not self.store_xml(newnode):
self.store_xml(oldnode)
return False
return True
def is_used(self,node_id):
return node_id in self._id_store
def save(self,node_id):
if not node_id: return
self._id_store[node_id] = 1
def remove(self,node_id):
if not node_id: return
try:
del self._id_store[node_id]
except KeyError:
pass
def clear(self):
self._id_store = {}
def id_in_use(obj_id):
global id_store
if id_store.is_used(obj_id):
id_used_err(obj_id)
return True
return False
def find_cib_node(cib,tag):
'''Find an element which is a child of configuration.'''
for node in cib.childNodes:
if is_element(node) and node.tagName == "configuration":
for node2 in node.childNodes:
if is_element(node2) and node2.tagName == tag:
return node2
return None
#
# CLI parsing utilities
# WARNING: ugly code ahead (to be replaced some day by a proper
# yacc parser, if there's such a thing)
#
def cli_parse_rsctype(s, pl = []):
ra_class = "ocf"
provider = "heartbeat"
list = s.split(':')
if len(list) == 3:
ra_class,provider,rsc_type = list
elif len(list) == 2:
ra_class,rsc_type = list
elif len(list) == 1:
rsc_type = list[0]
else:
return None
pl.append(["class",ra_class])
pl.append(["provider",provider])
pl.append(["type",rsc_type])
def cli_parse_attr(s,pl=[]):
if s and '=' in s[0]:
n,v = s[0].split('=',1)
pl.append([n,v])
cli_parse_attr(s[1:],pl)
def parse_resource(s):
el_type = s[0]
attr_lists_keyw = ["params","meta"]
cli_list = []
# the head
head = []
head.append(["id",s[1]])
i = 3
if el_type == "primitive":
cli_parse_rsctype(s[2],head)
if not find_value(head,"type"):
syntax_err(s[2:])
return None
else:
cl = []
cl.append(s[2])
if el_type == "group":
while i < len(s):
if s[i] in attr_lists_keyw:
break
else:
cl.append(s[i])
i += 1 # skip to the next token
head.append(["$children",cl])
cli_list.append([el_type,head])
# the rest
state = 0 # 1: reading operations; 2: operations read
while len(s) > i+1:
pl = []
keyw = s[i]
if keyw in attr_lists_keyw:
if state == 1:
state = 2
attr_lists_keyw.remove(keyw)
elif el_type == "primitive" and state == 0 and keyw == "operations":
state = 1
elif el_type == "primitive" and state <= 1 and keyw == "op":
if state == 0:
state = 1
pl.append(["name",s[i+1]])
else:
syntax_err(s[i:])
return None
if keyw == "op":
if len(s) > i+2:
cli_parse_attr(s[i+2:],pl)
else:
cli_parse_attr(s[i+1:],pl)
if len(pl) == 0:
syntax_err(s[i:])
return None
i += len(pl)+1
# interval is obligary for ops, supply 0 if not there
if keyw == "op" and not find_value(pl,"interval"):
pl.append(["interval","0"])
cli_list.append([keyw,pl])
if len(s) > i:
syntax_err(s[i:])
return None
return cli_list
score_type = {'advisory': '0','mandatory': 'inf'}
def cli_parse_score(score,pl):
import re
if score in score_type:
pl.append(["score",score_type[score]])
elif re.match("^[+-]?(inf|infinity|[0-9]+)$",score):
pl.append(["score",score])
else:
pl.append(["score_attribute",score])
return True
binary_ops = ('lt','gt','lte','gte','eq','ne')
binary_types = ('string' , 'version' , 'number')
unary_ops = ('defined','not_defined')
def is_binary_op(s):
l = s.split(':')
if len(l) == 2:
return l[0] in binary_types and l[1] in binary_ops
elif len(l) == 1:
return l[0] in binary_ops
else:
return False
def set_binary_op(s,pl):
l = s.split(':')
if len(l) == 2:
pl.append(["type",l[0]])
pl.append(["operation",l[1]])
else:
pl.append(["operation",l[0]])
def cli_get_expression(s,pl):
if len(s) > 1 and s[0] in unary_ops:
pl.append(["operation",s[0]])
pl.append(["attribute",s[1]])
elif len(s) > 2 and is_binary_op(s[1]):
pl.append(["attribute",s[0]])
set_binary_op(s[1],pl)
pl.append(["value",s[2]])
else:
return False
return True
def parse_rule(s):
if s[0] != "rule":
return 0,None
rule_list = []
head_pl = []
rule_list.append([s[0],head_pl])
i = 1
cli_parse_attr(s[i:],head_pl)
i += len(head_pl)
if find_value(head_pl,"$id-ref"):
return i,rule_list
if not cli_parse_score(s[i],head_pl):
return i,None
i += 1
bool_op = ''
while len(s) > i+1:
pl = []
if not cli_get_expression(s[i:],pl):
syntax_err(s[i:])
return i,None
rule_list.append(["expression",pl])
i += len(pl)
if len(s) > i and s[i] in ('or','and'):
if bool_op and bool_op != s[i]:
common_err("rule contains different bool operations: %s" % ' '.join(s))
return i,None
else:
bool_op = s[i]
i += 1
if len(s) > i and s[i] == "rule":
break
if bool_op:
head_pl.append(["boolean_op",bool_op])
return i,rule_list
def parse_location(s):
cli_list = []
head_pl = []
head_pl.append(["id",s[1]])
head_pl.append(["rsc",s[2]])
cli_list.append([s[0],head_pl])
i = 3
while i < len(s):
numtoks,l = parse_rule(s[i:])
if not l:
return None
cli_list += l
i += numtoks
if len(s) < i:
syntax_err(s[i:])
return None
return cli_list
def cli_opt_symmetrical(s,pl):
if not s:
return True
pl1 = []
cli_parse_attr(s,pl1)
if len(pl1) != 1 or not find_value(pl1,"symmetrical"):
syntax_err(s)
return False
pl += pl1
return True
roles = ('Stopped', 'Started', 'Master', 'Slave')
def cli_parse_rsc_role(s,pl,attr_pfx = ''):
l = s.split(':')
pl.append([attr_pfx+"rsc",l[0]])
if len(l) == 2:
if l[1] not in roles:
common_err("bad resource role definition: %s"%s)
return False
pl.append([attr_pfx+"rsc-role",l[1]])
elif len(l) > 2:
common_err("bad resource role definition: %s"%s)
return False
return True
def parse_colocation(s):
cli_list = []
head_pl = []
cli_list.append([s[0],head_pl])
if len(s) < 5 or len(s) > 6:
syntax_err(s)
return None
head_pl.append(["id",s[1]])
if not cli_parse_score(s[2],head_pl):
return None
if not cli_parse_rsc_role(s[3],head_pl):
return None
if not cli_parse_rsc_role(s[4],head_pl,'with-'):
return None
if not cli_opt_symmetrical(s[5:],head_pl):
return None
return cli_list
actions = ( 'start', 'promote', 'demote', 'stop')
def cli_parse_rsc_action(s,pl,rsc_pos):
l = s.split(':')
pl.append([rsc_pos,l[0]])
if len(l) == 2:
if l[1] not in actions:
common_err("bad resource action definition: %s"%s)
return False
pl.append([rsc_pos+"-action",l[1]])
elif len(l) > 1:
common_err("bad resource action definition: %s"%s)
return False
return True
def parse_order(s):
cli_list = []
head_pl = []
cli_list.append([s[0],head_pl])
if len(s) < 5 or len(s) > 6:
syntax_err(s)
return None
head_pl.append(["id",s[1]])
if not cli_parse_score(s[2],head_pl):
return None
if not cli_parse_rsc_action(s[3],head_pl,'first'):
return None
if not cli_parse_rsc_action(s[4],head_pl,'then'):
return None
if not cli_opt_symmetrical(s[5:],head_pl):
return None
return cli_list
def parse_constraint(s):
if s[0] == "location":
return parse_location(s)
elif s[0] == "colocation":
return parse_colocation(s)
elif s[0] == "order":
return parse_order(s)
def parse_property(s):
cli_list = []
head_pl = []
cli_list.append([s[0],head_pl])
cli_parse_attr(s[1:],head_pl)
if len(head_pl) < 0 or len(s) > len(head_pl)+1:
syntax_err(s)
return None
return cli_list
def parse_cli(s):
'''
Input: a list of tokens (or a CLI format string).
Return: a list of items; each item is a tuple
with two members: a string (tag) and a nvpairs or
attributes dict.
'''
if type(s) == type(u''):
s = s.encode('ascii')
if type(s) == type(''):
s = shlex.split(s)
while '\n' in s:
s.remove('\n')
if s and s[0].startswith('#'):
return None
if len(s) > 1 and s[0] == "property":
return parse_property(s)
if len(s) < 3: # we want at least two tokens
syntax_err(s)
return None
if is_resource_cli(s[0]):
return parse_resource(s)
elif is_constraint_cli(s[0]):
return parse_constraint(s)
else:
syntax_err(s)
return None
#
# XML generate utilities
#
hints_list = {
"instance_attributes": "params",
"meta_attributes": "meta-options",
"operations": "ops",
"rule": "rule",
"expression": "expression",
}
match_list = {
"crm_config": (),
"cluster_property_set": (),
"instance_attributes": (),
"meta_attributes": (),
"operations": (),
"nvpair": ("name",),
"op": ("name","interval"),
"rule": ("score","score_attribute","role"),
"expression": ("attribute","operation","value"),
}
def lookup_node(node,oldnode):
'''
Find a child of oldnode which matches node.
'''
global match_list
#print "match:",node.tagName,oldnode.getAttribute("id")
if not oldnode:
return None
try:
attr_list = match_list[node.tagName]
except KeyError:
attr_list = []
for c in oldnode.childNodes:
if is_element(c) and node.tagName == c.tagName:
failed = False
for a in attr_list:
if node.getAttribute(a) != c.getAttribute(a):
failed = True
break
if not failed:
return c
return None
def set_id(node,oldnode,id_hint):
global id_store
id = node.getAttribute("id") or \
(oldnode and oldnode.getAttribute("id"))
if not id:
id = id_store.new(node,id_hint)
else:
id_store.save(id)
node.setAttribute("id",id)
def mkxmlsimple(e,oldnode,id_hint):
'''
Create an xml node from the (name,dict) pair. The name is the
name of the element. The dict contains a set of attributes.
'''
global cib_factory
node = cib_factory.createElement(e[0])
for n,v in e[1]:
if n == "$children": # this one's skipped
continue
if n.startswith('$'):
n = n.lstrip('$')
if (type(v) != type('') and type(v) != type(u'')) \
or v: # skip empty strings
node.setAttribute(n,v)
set_id(node,lookup_node(node,oldnode),id_hint)
return node
def find_value(pl,name):
for n,v in pl:
if n == name:
return v
return None
def mkxmlnvpairs(e,oldnode,id_hint):
'''
Create xml from the (name,dict) pair. The name is the name of
the element. The dict contains a set of nvpairs. Stuff such
as instance_attributes.
NB: Other tags not containing nvpairs are fine if the dict is empty.
'''
global hints_list
global cib_factory
node = cib_factory.createElement(e[0])
match_node = lookup_node(node,oldnode)
v = find_value(e[1],"$id")
if v:
node.setAttribute("id",v)
e[1].remove(["$id",v])
else:
set_id(node,match_node,id_hint)
v = find_value(e[1],"$id-ref")
if v:
node.setAttribute("id-ref",v)
e[1].remove(["$id-ref",v])
try:
hint = hints_list[e[0]]
except: hint = ''
hint = hint and "%s_%s" % (id_hint,hint) or id_hint
nvpair_pfx = node.getAttribute("id") or hint
for n,v in e[1]:
nvpair = cib_factory.createElement("nvpair")
node.appendChild(nvpair)
nvpair.setAttribute("name",n)
nvpair.setAttribute("value",v)
set_id(nvpair,lookup_node(nvpair,match_node),nvpair_pfx)
return node
def mkxmlop(e,oldnode,id_hint):
'''
Create an operation xml from the (name,dict) pair.
'''
global cib_factory
node = cib_factory.createElement(e[0])
for n,v in e[1]:
node.setAttribute(n,v)
tmp = cib_factory.createElement("operations")
oldops = lookup_node(tmp,oldnode) # first find old operations
set_id(node,lookup_node(node,oldops),id_hint)
return node
conv_list = {
"params": "instance_attributes",
"meta": "meta_attributes",
"property": "cluster_property_set"
}
def mkxmlnode(e,oldnode,id_hint):
'''
Create xml from the (name,dict) pair. The name is the name of
the element. The dict contains either a set of nvpairs or a
set of attributes. The id is either generated or copied if
found in the provided xml. Stuff such as instance_attributes.
'''
if e[0] in conv_list:
e[0] = conv_list[e[0]]
if e[0] in ("instance_attributes","meta_attributes","operations","cluster_property_set"):
return mkxmlnvpairs(e,oldnode,id_hint)
elif e[0] == "op":
return mkxmlop(e,oldnode,id_hint)
else:
return mkxmlsimple(e,oldnode,id_hint)
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)
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,nodes,resources,constraints
def set_property(set_node,name,value):
global cib_factory
id = set_node.getAttribute("id")
for c in set_node.childNodes:
if is_element(c) and c.getAttribute("name") == name:
c.setAttribute("value",value)
return
np = cib_factory.createElement("nvpair")
np.setAttribute("id","%s-%s"%(id,name))
np.setAttribute("name",value)
np.setAttribute("value",value)
set_node.appendChild(np)
def xml_cmp(n,m):
#if hash(n.toxml()) != hash(m.toxml()):
#print n.toprettyxml()
#print m.toprettyxml()
return hash(n.toxml()) == hash(m.toxml())
def get_interesting_nodes(node,nodes = []):
global cib_object_map
for c in node.childNodes:
if is_element(c) and c.tagName in cib_object_map:
nodes.append(c)
get_interesting_nodes(c,nodes)
return nodes
def filter_on_tag(nl,tag):
return [node for node in nl if node.tagName == tag]
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")
def processing_sort(nl):
'''
It's usually important to process cib objects in this order,
i.e. simple objects first.
'''
return primitives(nl) + groups(nl) + mss(nl) + clones(nl) \
+ constraints(nl) + properties(nl)
def filter_on_type(cl,obj_type):
if type(cl[0]) == type([]):
return [cli_list for cli_list in cl if cli_list[0][0] == obj_type]
else:
return [obj for obj in cl if obj.obj_type == obj_type]
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")
def constraints_cli(node_list):
return filter_on_type(node_list,"location") \
+ filter_on_type(node_list,"colocation") \
+ filter_on_type(node_list,"order")
def properties_cli(cl):
return filter_on_type(cl,"property")
def processing_sort_cli(cl):
return primitives_cli(cl) + groups_cli(cl) + mss_cli(cl) + clones_cli(cl) \
+ constraints_cli(cl) + properties_cli(cl)
#
# CLI format generation utilities (from XML)
#
def cli_pairs(pl):
'Return a string of name="value" pairs (passed in a list of pairs).'
l = []
for n,v in pl:
l.append('%s="%s"'%(n,v))
return ' '.join(l)
def nvpairs2list(node):
"Convert nvpairs to a list of pairs."
pl = []
for c in node.getElementsByTagName("nvpair"):
name = c.getAttribute("name")
value = c.getAttribute("value")
pl.append([name,value])
return pl
def op2cli(node):
pl = []
for name in node.attributes.keys():
if name == "name":
action = node.getAttribute(name)
elif name != "id": # skip the id
pl.append([name,node.getAttribute(name)])
return "op %s %s"%(action,cli_pairs(pl))
def cli_operations(node):
s = []
node_id = node.getAttribute("id")
if node_id:
s.append("operations $id=%s" % node_id)
else:
idref = node.getAttribute("id-ref")
if idref:
s.append("operations $id-ref=%s" % idref)
for c in node.childNodes:
if is_element(c) and c.tagName == "op":
s.append(op2cli(c))
return ' \\\n\t'.join(s)
def exp2cli(node):
operation = node.getAttribute("operation")
attribute = node.getAttribute("attribute")
value = node.getAttribute("value")
if not value:
return "%s %s" % (operation,attribute)
else:
return "%s %s %s" % (attribute,operation,value)
def get_score(node):
score = node.getAttribute("score")
if not score:
return node.getAttribute("score_attribute")
if score.find("infinity") >= 0:
score = score.replace("infinity","INFINITY")
elif score.find("inf") >= 0:
score = score.replace("inf","INFINITY")
return score
def cli_rule(node):
s = []
node_id = node.getAttribute("id")
if node_id:
s.append("$id=%s" % node_id)
else:
idref = node.getAttribute("id-ref")
if idref:
return "$id-ref=%s" % idref
s.append(get_score(node))
bool_op = node.getAttribute("boolean_op")
if not bool_op:
bool_op = "and"
exp = []
for c in node.childNodes:
if is_element(c) and c.tagName == "expression":
exp.append(exp2cli(c))
expression = (" %s "%bool_op).join(exp)
return "%s %s" % (' '.join(s),expression)
def primitive_head(node):
global cib_object_map
obj_type = cib_object_map[node.tagName][0]
node_id = node.getAttribute("id")
ra_type = node.getAttribute("type")
ra_class = node.getAttribute("class")
ra_provider = node.getAttribute("provider")
s1 = s2 = ''
if ra_class:
s1 = "%s:"%ra_class
if ra_provider:
s2 = "%s:"%ra_provider
return "%s %s %s"%(obj_type,node_id,''.join((s1,s2,ra_type)))
def cont_head(node):
global cib_object_map
obj_type = cib_object_map[node.tagName][0]
node_id = node.getAttribute("id")
children = []
for c in node.childNodes:
if not is_element(c):
continue
if (obj_type == "group" and c.tagName == "primitive") or \
c.tagName in ("primitive","group"):
children.append(c.getAttribute("id"))
elif obj_type in ("clone","ms") and \
c.tagName in ("primitive","group"):
children.append(c.getAttribute("id"))
return "%s %s %s"%(obj_type,node_id,' '.join(children))
def location_head(node):
global cib_object_map
obj_type = cib_object_map[node.tagName][0]
node_id = node.getAttribute("id")
rsc = node.getAttribute("rsc")
return "%s %s %s"%(obj_type,node_id,rsc)
def mkrscrole(node,n):
rsc = node.getAttribute(n)
rsc_role = node.getAttribute(n + "-role")
if rsc_role:
return "%s:%s"%(rsc,rsc_role)
else:
return rsc
def mkrscaction(node,n):
rsc = node.getAttribute(n)
rsc_action = node.getAttribute(n + "-action")
if rsc_action:
return "%s:%s"%(rsc_action,rsc)
else:
return rsc
def simple_constraint_head(node):
global cib_object_map
obj_type = cib_object_map[node.tagName][0]
node_id = node.getAttribute("id")
score = get_score(node)
col = []
if obj_type == "colocation":
col.append(mkrscrole(node,"rsc"))
col.append(mkrscrole(node,"with-rsc"))
else:
col.append(mkrscaction(node,"first-rsc"))
col.append(mkrscaction(node,"then-rsc"))
symm = node.getAttribute("symmetrical")
if symm:
col.append("symmetrical=%s"%symm)
return "%s %s %s"%(obj_type,node_id,' '.join(col))
class CibObjectSet(object):
'''
Edit or display a set of cib objects.
repr() for objects representation and
save() used to store objects into internal structures
are defined in subclasses.
'''
def __init__(self, *args):
self.obj_list = []
def _write_tmp(self,s):
fd,tmp = mkstemp()
f = os.fdopen(fd,"w")
f.write(s)
f.close()
return tmp
def _open_url(self,src):
import urllib
try:
return urllib.urlopen(src)
except:
pass
try:
return open(src)
except:
pass
common_err("could not open %s" % src)
return None
def edit(self):
if batch:
common_info("edit not allowed in batch mode")
return
err_buf.buffer() # keep error messages
tmp = self._write_tmp(self.repr())
filehash = hash(self.repr())
err_buf.release() # show them, but get an ack from the user
while True:
if edit_file(tmp) != 0:
break
f = open(tmp,'r')
s = ''.join(f)
f.close()
if hash(s) == filehash: # file unchanged
break
if interactive and not self.save(s):
if ask("There was a parsing problem. Do you want to edit again?"):
continue
break
os.unlink(tmp)
def import_template(self,template):
global cib_factory
f = self._open_url(template)
if not f:
return
s = ''.join(f)
f.close()
tmp = self._write_tmp(s)
if edit_file(tmp) != 0:
os.unlink(tmp)
return
f = open(tmp,'r')
s = ''.join(f)
f.close()
if interactive:
if not ask("Do you want to commit the changes (the existing CIB will be erased)?"):
return
cib_factory.erase()
self.save(s)
os.unlink(tmp)
def save_to_file(self,file):
if interactive and os.access(file,os.F_OK):
if not ask("File %s exists. Do you want to overwrite it?"%file):
return
f = open(file,"w")
f.write(self.repr())
f.write('\n')
f.close()
def show(self):
err_buf.buffer() # keep error messages
s = self.repr()
err_buf.release() # show them, but get an ack from the user
page_string(s)
def import_file(self,method,file):
if method == "replace":
if interactive and cib_factory.has_cib_changed():
if not ask("This operation will erase all changes. Do you want to proceed?"):
return
cib_factory.erase()
f = self._open_url(file)
if not f:
return
s = ''.join(f)
f.close()
self.save(s)
def repr(self):
pass
def save(self,s):
'''
For each object:
- try to find a corresponding object in obj_list
- if not found: create new
- if found: replace the object in the obj_list with
the new object
See below for specific implementations.
'''
pass
def lookup_cli(self,cli_list):
for obj in self.obj_list:
if obj.matchcli(cli_list):
return obj
def lookup(self,obj_type,obj_id):
for obj in self.obj_list:
if obj.match(obj_type,obj_id):
return obj
def drop_remaining(self):
'Any remaining objects in obj_list are deleted.'
global cib_factory
for obj in self.obj_list:
cib_factory.delete(obj.obj_id)
class CibObjectSetCli(CibObjectSet):
'''
Edit or display a set of cib objects (using cli notation).
'''
def __init__(self, *args):
global cib_factory
CibObjectSet.__init__(self, *args)
self.obj_list = cib_factory.mkobj_list("cli",*args)
def repr(self):
"Return a string containing cli format of all objects."
if not self.obj_list:
return ''
return '\n'.join(obj.repr_cli() \
for obj in processing_sort_cli(self.obj_list))
def process(self,cli_list):
global cib_factory
obj = self.lookup_cli(cli_list)
if obj:
obj.update_from_cli(cli_list)
self.obj_list.remove(obj)
else:
cib_factory.create_from_cli(cli_list)
def save(self,s):
l = []
for cli_text in lines2cli(s):
cli_list = parse_cli(cli_text)
if cli_list:
l.append(cli_list)
if l:
for cli_list in processing_sort_cli(l):
self.process(cli_list)
self.drop_remaining()
return True
class CibObjectSetRaw(CibObjectSet):
'''
Edit or display one or more CIB objects (XML).
'''
def __init__(self, *args):
global cib_factory
CibObjectSet.__init__(self, *args)
self.obj_list = cib_factory.mkobj_list("xml",*args)
def repr(self):
"Return a string containing xml of all objects."
doc = cib_factory.objlist2doc(self.obj_list)
s = doc.toprettyxml(user_prefs.xmlindent)
doc.unlink()
return s
def process(self,node):
global cib_factory
obj = self.lookup(node.tagName,node.getAttribute("id"))
if obj:
obj.update_from_node(node)
self.obj_list.remove(obj)
else:
cib_factory.create_from_node(node)
def save(self,s):
try:
doc = xml.dom.minidom.parseString(s)
except xml.parsers.expat.ExpatError,msg:
cib_parse_err(msg)
return False
sanitize_cib(doc)
newnodes = get_interesting_nodes(doc,[])
if newnodes:
for node in processing_sort(newnodes):
self.process(node)
self.drop_remaining()
doc.unlink()
return True
def verify(self):
pipe_string(cib_verify,self.repr())
class CibObject(object):
'''
The top level object of the CIB. Resources and constraints.
'''
def __init__(self,xml_obj_type,obj_id = None):
global cib_object_map
self.backtrans = {} # generate a translation cli -> tag
for key in cib_object_map:
self.backtrans[cib_object_map[key][0]] = key
if xml_obj_type in cib_object_map:
self.obj_type = cib_object_map[xml_obj_type][0]
self.parent_type = cib_object_map[xml_obj_type][2]
self.xml_obj_type = xml_obj_type
else:
unsupported_err(xml_obj_type)
return
self.origin = "" # where did it originally come from?
self.nocli = False # we don't support this one
self.updated = False # was the object updated
self.parent = None # object superior (group/clone/ms)
self.children = [] # objects inferior
if obj_id:
self.new(obj_id)
else:
self.obj_id = None
self.node = None
def save_xml(self,node):
self.obj_id = node.getAttribute("id")
self.node = node
return self.cli_use_validate()
def new(self,obj_id):
global cib_factory
if id_in_use(obj_id):
return
tag = self.xml_obj_type
self.node = cib_factory.createElement(tag)
self.obj_id = obj_id
self.node.setAttribute("id",self.obj_id)
self.origin = "user"
def attr_exists(self,attr):
if not attr in self.node.attributes.keys():
no_attribute_err(attr,self.obj_id)
return False
return True
def cli_use_validate(self):
'''
Check validity of the object, as we know it. It may
happen that we don't recognize a construct, but that the
object is still valid for the CRM. In that case, the
object is marked as "CLI read only", i.e. we will neither
convert it to CLI nor try to edit it in that format.
The validation procedure:
we convert xml to cli and then back to xml. If the two
xml representations match then we can understand the xml.
'''
if not self.node:
return True
if not self.attr_exists("id"):
return False
cli_text = self.repr_cli()
if not cli_text:
return False
xml2 = self.cli2node(cli_text)
if not xml2:
return False
rc = xml_cmp(self.node,xml2)
xml2.unlink()
return rc
def matchcli(self,cli_list):
head = cli_list[0]
return self.obj_type == head[0] \
and self.obj_id == find_value(head[1],"id")
def match(self,obj_type,obj_id):
return self.obj_type == obj_type and self.obj_id == obj_id
def obj_string(self):
return "%s:%s" % (self.obj_type,self.obj_id)
def propagate_updated(self,v):
p = self.parent
while p:
p.updated = v
p = p.parent
def update_from_cli(self,cli_list):
'Update ourselves from the cli intermediate.'
global id_store
global cib_factory
oldnode = self.node
id_store.remove_xml(oldnode)
newnode = self.cli2node(cli_list)
if not newnode:
id_store.replace_xml(newnode,oldnode)
return
if xml_cmp(oldnode,newnode):
newnode.unlink()
return # the new and the old versions are equal
oldnode.parentNode.replaceChild(newnode,oldnode)
self.node = newnode
head = cli_list[0]
obj_type = head[0]
obj_id = find_value(head[1],"id")
v = find_value(head[1],"$children")
if v:
if not cib_factory.check_children(v,obj_type,obj_id):
id_store.replace_xml(newnode,oldnode)
newnode.unlink()
return
cib_factory.adjust_children(self,v)
oldnode.unlink()
self.updated = True
self.propagate_updated(True)
def update_from_node(self,node):
'Update ourselves from a doc node.'
global id_store
global cib_factory
if not node:
return
oldxml = self.node
newxml = node
if xml_cmp(oldxml,newxml):
return # the new and the old versions are equal
if not id_store.replace_xml(oldxml,newxml):
return
oldxml.unlink()
self.node = cib_factory.doc.importNode(newxml,1)
cib_factory.topnode[self.parent_type].appendChild(self.node)
cib_factory.update_links(self)
self.updated = True
self.propagate_updated(True)
def find_child_in_node(self,child):
for c in self.node.childNodes:
if not is_element(c):
continue
if c.tagName == child.obj_type and \
c.getAttribute("id") == child.obj_id:
return c
return None
class CibPrimitive(CibObject):
'''
Primitives.
'''
def repr_cli(self,node = None):
if not node:
node = self.node
l = []
l.append(primitive_head(node))
for c in node.childNodes:
if not is_element(c):
continue
if c.tagName == "instance_attributes":
l.append("params %s" % cli_pairs(nvpairs2list(c)))
elif c.tagName == "meta_attributes":
l.append("meta %s" % cli_pairs(nvpairs2list(c)))
elif c.tagName == "operations":
l.append(cli_operations(c))
return ' \\\n\t'.join(l)
def cli2node(self,cli,oldnode = None):
'''
Convert a CLI description to DOM node.
Try to preserve as many ids as possible in case there's
an old XML version.
'''
if type(cli) == type('') or type(cli) == type(u''):
cli_list = parse_cli(cli)
else:
cli_list = cli
if not cli_list:
return None
if not oldnode:
oldnode = self.node
head = copy.copy(cli_list[0])
head[0] = self.backtrans[head[0]]
headnode = mkxmlsimple(head,oldnode,'rsc')
id_hint = headnode.getAttribute("id")
operations = None
for e in cli_list[1:]:
n = mkxmlnode(e,oldnode,id_hint)
if e[0] == "operations":
operations = n
if e[0] != "op":
headnode.appendChild(n)
else:
if not operations:
operations = mkxmlnode(["operations",{}],oldnode,id_hint)
headnode.appendChild(operations)
operations.appendChild(n)
return headnode
class CibContainer(CibObject):
'''
Groups and clones and ms.
'''
def repr_cli(self,node = None):
if not node:
node = self.node
l = []
l.append(cont_head(node))
for c in node.childNodes:
if not is_element(c):
continue
if c.tagName == "instance_attributes":
l.append("params %s" % cli_pairs(nvpairs2list(c)))
elif c.tagName == "meta_attributes":
l.append("meta %s" % cli_pairs(nvpairs2list(c)))
return ' \\\n\t'.join(l)
def cli2node(self,cli,oldnode = None):
if type(cli) == type('') or type(cli) == type(u''):
cli_list = parse_cli(cli)
else:
cli_list = cli
if not cli_list:
return None
if not oldnode:
oldnode = self.node
head = copy.copy(cli_list[0])
head[0] = self.backtrans[head[0]]
headnode = mkxmlsimple(head,oldnode,'grp')
id_hint = headnode.getAttribute("id")
for e in cli_list[1:]:
n = mkxmlnode(e,oldnode,id_hint)
headnode.appendChild(n)
v = find_value(head[1],"$children")
if v:
for child_id in v:
obj = cib_factory.find_object(child_id)
if obj:
n = obj.node.cloneNode(1)
headnode.appendChild(n)
else:
no_object_err(child_id)
return headnode
class CibLocation(CibObject):
'''
Location constraint.
'''
def repr_cli(self,node = None):
if not node:
node = self.node
l = []
l.append(location_head(node))
for c in node.childNodes:
if not is_element(c):
continue
if c.tagName == "rule":
l.append("rule %s" % cli_rule(c))
return ' \\\n\t'.join(l)
def cli2node(self,cli,oldnode = None):
if type(cli) == type('') or type(cli) == type(u''):
cli_list = parse_cli(cli)
else:
cli_list = cli
if not cli_list:
return None
if not oldnode:
oldnode = self.node
head = copy.copy(cli_list[0])
head[0] = self.backtrans[head[0]]
headnode = mkxmlsimple(head,oldnode,'location')
id_hint = headnode.getAttribute("id")
for e in cli_list[1:]:
if e[0] == "expression":
n = mkxmlnode(e,oldrule,id_hint)
else:
n = mkxmlnode(e,oldnode,id_hint)
if e[0] == "rule":
rule = n
headnode.appendChild(n)
oldrule = lookup_node(rule,oldnode)
else:
rule.appendChild(n)
return headnode
class CibSimpleConstraint(CibObject):
'''
Colocation and order constraints.
'''
def repr_cli(self,node = None):
if not node:
node = self.node
return simple_constraint_head(node)
def cli2node(self,cli,oldnode = None):
if type(cli) == type('') or type(cli) == type(u''):
cli_list = parse_cli(cli)
else:
cli_list = cli
if not cli_list:
return None
if not oldnode:
oldnode = self.node
head = copy.copy(cli_list[0])
head[0] = self.backtrans[head[0]]
headnode = mkxmlsimple(head,oldnode,'')
return headnode
default_cluster_property_set = "cib-bootstrap-options"
class CibProperty(CibObject):
'''
Cluster properties.
'''
def repr_cli(self,node = None):
if not node:
node = self.node
l = []
l.append('property $id="%s"' % node.getAttribute("id"))
properties = nvpairs2list(node)
for n,v in properties:
l.append('%s="%s"' % (n,v))
return ' \\\n\t'.join(l)
def cli2node(self,cli,oldnode = None):
if type(cli) == type('') or type(cli) == type(u''):
cli_list = parse_cli(cli)
else:
cli_list = cli
if not cli_list:
return None
if not oldnode:
oldnode = cib_factory.topnode["crm_config"]
head = copy.copy(cli_list[0])
head[0] = self.backtrans[head[0]]
id = find_value(head[1],"$id")
if not id:
id = default_cluster_property_set
headnode = mkxmlnode(head,oldnode,id)
return headnode
def matchcli(self,cli_list):
head = cli_list[0]
return self.obj_type == head[0] \
and self.obj_id == find_value(head[1],"$id")
# xml -> cli translations (and classes)
cib_object_map = {
"primitive": ( "primitive", CibPrimitive, "resources" ),
"group": ( "group", CibContainer, "resources" ),
"clone": ( "clone", CibContainer, "resources" ),
"master": ( "ms", CibContainer, "resources" ),
"rsc_location": ( "location", CibLocation, "constraints" ),
"rsc_colocation": ( "colocation", CibSimpleConstraint, "constraints" ),
"rsc_order": ( "order", CibSimpleConstraint, "constraints" ),
"cluster_property_set": ( "property", CibProperty, "crm_config" ),
}
class CibFactory(object):
'''
Juggle with CIB objects.
See check_structure below for details on the internal cib
representation.
'''
def __init__(self, cibfile=None):
self.doc = None # the cib
self.topnode = {}
self.topnode["resources"] = None # the resources node
self.topnode["constraints"] = None # the constraints node
self.topnode["crm_config"] = None # the crm_config node
self.cib_attributes = ('epoch', 'admin_epoch', 'num_updates', 'validate-with', 'crm_feature_set')
self.cib_attrs = {} # cib version dictionary
self.cib_objects = [] # a list of cib objects
self.remove_queue = [] # a list of cib objects to be removed
self.cibfile = cibfile # for testing
self.overwrite = False # update cib unconditionally
def check_topnode(self,obj):
if not obj.node.parentNode.isSameNode(self.topnode[obj.parent_type]):
common_err("object %s is not linked to %s"%(obj.obj_id,obj.parent_type))
def check_parent(self,obj,parent):
if not obj in parent.children:
common_err("object %s does not reference its child %s"%(parent.obj_id,obj.obj_id))
if not parent.node.isSameNode(obj.node.parentNode):
common_err("object %s node is not a child of its parent %s, but %s:%s"%(obj.obj_id,parent.obj_id,obj.node.tagName,obj.node.getAttribute("id")))
def check_structure(self):
#print "Checking structure..."
for obj in self.cib_objects:
#print "Checking %s... (%s)" % (obj.obj_id,obj.nocli)
if obj.parent:
self.check_parent(obj,obj.parent)
else:
self.check_topnode(obj)
for child in obj.children:
self.check_parent(child,child.parent)
def createElement(self,tag):
if self.doc:
return self.doc.createElement(tag)
def check_cib_release(self,cib):
#req = cib.getAttribute("cib_feature_revision")
req = cib.getAttribute("crm_feature_set")
if not req or float(req) < 3.0:
cib_ver_unsupported_err(req)
return False
return True
def parse_cib(self):
if self.cibfile:
file = open(self.cibfile, "r")
cib = ''.join(file)
file.close()
else: # use cibadmin
cib = '\n'.join(os_system(cib_dump))
if not cib:
common_err("invoking cibadmin failed")
return False
try:
self.doc = xml.dom.minidom.parseString(cib)
except xml.parsers.expat.ExpatError,msg:
cib_parse_err(msg)
return False
cib = self.doc.childNodes[0]
if not is_element(cib) or cib.tagName != "cib":
common_err("CIB contains no 'cib' element!")
self.reset()
return False
if not self.check_cib_release(cib):
self.reset()
return False
for attr in self.cib_attributes:
self.cib_attrs[attr] = cib.getAttribute(attr)
for n in ("resources","constraints","crm_config"):
self.topnode[n] = find_cib_node(cib,n)
if not self.topnode[n]:
common_err("CIB contains no '%s' element!"%n)
self.reset()
return False
return True
def chk_attribute(self,c,a):
return self.cib_attrs[a] and \
c.getAttribute(a) == self.cib_attrs[a]
def commit(self,force = False):
'Commit the configuration to the CIB.'
# 0. ensure that cib didn't change in the meantime
if not force and not self.overwrite:
cib = '\n'.join(os_system(cib_dump))
if not cib:
common_err("cibadmin failed")
return
doc = xml.dom.minidom.parseString(cib)
if not doc:
common_err("could not read current cib!")
return
cib = doc.childNodes[0]
if not is_element(cib) or cib.tagName != "cib":
common_err("current cib has no 'cib' element!")
doc.unlink()
return
if not self.chk_attribute(cib,'epoch') or \
not self.chk_attribute(cib,'admin_epoch'):
common_warn("cib changed in the meantime: won't touch it!")
common_info("use 'commit force' to force the changes.")
doc.unlink()
return
doc.unlink()
cnt = 0
# 1. remove objects
cnt += self.delete_objects()
# 2. create objects
cnt += self.commit_objects(lambda o: o.origin == 'user','-C')
# 3. replace objects
cnt += self.commit_objects(lambda o: o.origin != 'user' and o.updated,'-R')
# reload the cib!
if force:
self.reset()
self.initialize()
elif cnt:
epoch = self.cib_attrs['epoch']
self.cib_attrs['epoch'] = "%d"%(int(epoch)+cnt)
def set_cib_attributes(self,cib):
for attr in self.cib_attributes:
cib.setAttribute(attr,self.cib_attrs[attr])
def objlist2doc(self,obj_list,filter = None):
'''
Return document containing objects in obj_list.
Must remove all children from the object list, because
printing xml of parents will include them.
Optional filter to sieve objects.
'''
doc,cib,crm_config,nodes,resources,constraints = new_cib()
for obj in [obj for obj in obj_list if not obj.parent]:
if filter and not filter(obj):
continue
i_node = doc.importNode(obj.node,1)
if obj.parent_type == "resources":
resources.appendChild(i_node)
elif obj.parent_type == "constraints":
constraints.appendChild(i_node)
elif obj.parent_type == "crm_config":
crm_config.appendChild(i_node)
self.set_cib_attributes(cib)
return doc
def delete_objects(self):
cnt = 0
if not self.remove_queue:
return cnt
obj_list = processing_sort_cli(self.remove_queue)
for obj in reversed(obj_list):
node = self.createElement(obj.xml_obj_type)
node.setAttribute("id",obj.obj_id)
#print node.toprettyxml()
rc = pipe_string("%s -D"%cib_piped,node.toxml())
if not rc:
self.remove_queue.remove(obj)
cnt += 1
else:
update_err(obj.obj_id,'-D',node)
node.unlink()
return cnt
def commit_objects(self,filter,cibadm_opt):
upd_list = []
for obj in self.cib_objects:
if not filter or filter(obj):
if not obj.parent and not obj in upd_list: # only top children!
upd_list.append(obj)
cnt = 0
if not upd_list:
return cnt
for obj in processing_sort_cli(upd_list):
#print obj.node.toprettyxml()
rc = pipe_string("%s %s -o %s" % \
(cib_piped,cibadm_opt,obj.parent_type),obj.node.toxml())
if not rc:
cnt += 1
self.obj_updated(obj)
else:
update_err(obj.obj_id,cibadm_opt,obj.node)
return cnt
def obj_updated(self,obj):
obj.updated = False
obj.origin = 'cib'
for child in obj.children:
self.obj_updated(child)
def update_links(self,obj):
'''
Update the structure links for the object (obj.children,
obj.parent). Update also the dom nodes, if necessary.
'''
obj.children = []
if obj.obj_type not in ("group","clone","ms"):
return
for c in obj.node.childNodes:
if is_element(c) and c.tagName in ("primitive","group"):
child = self.find_object_for_node(c)
if not child:
missing_obj_err(c)
continue
child.parent = obj
obj.children.append(child)
if not c.isSameNode(child.node):
child.node.parentNode.removeChild(child.node)
child.node.unlink()
child.node = c
def populate(self):
"Walk the cib and collect cib objects."
global cib_object_map
all_nodes = get_interesting_nodes(self.doc,[])
if not all_nodes:
return
for node in processing_sort(all_nodes):
obj = cib_object_map[node.tagName][1](node.tagName)
obj.origin = "cib"
self.cib_objects.append(obj)
if not obj.save_xml(node):
obj.nocli = True
for obj in self.cib_objects:
self.update_links(obj)
def initialize(self):
if self.doc:
return True
if not self.parse_cib():
common_err("bad cib")
return False
sanitize_cib(self.doc)
self.populate()
self.check_structure()
return True
def reset(self):
if not self.doc:
return
self.doc.unlink()
self.doc = None
self.topnode = {}
self.cib_attrs = {} # cib version dictionary
self.cib_objects = [] # a list of cib objects
self.remove_queue = [] # a list of cib objects to be removed
self.overwrite = False # update cib unconditionally
id_store.clear() # update cib unconditionally
def find_object(self,obj_id):
"Find an object."
for obj in self.cib_objects:
if obj.obj_id == obj_id:
return obj
return None
def find_object_for_node(self,node):
"Find an object which matches a dom node."
for obj in self.cib_objects:
if node.getAttribute("id") == obj.obj_id:
return obj
return None
def new_object(self,obj_type,obj_id):
"Create a new object of type obj_type."
global cib_object_map
if id_in_use(obj_id):
return None
for xml_obj_type,v in cib_object_map.items():
if v[0] == obj_type:
obj = v[1](xml_obj_type,obj_id)
if obj.obj_id:
return obj
else:
return None
return None
def filter(self,obj,*args):
"Filter objects."
if not args:
return True
if args[0] == "NOOBJ":
return False
if args[0] == "changed":
return obj.updated or obj.origin == "user"
return obj.obj_id in args
def mkobj_list(self,mode,*args):
obj_list = []
for obj in self.cib_objects:
f = lambda: self.filter(obj,*args)
if not f():
continue
if mode == "cli" and obj.nocli:
obj_cli_err(obj.obj_id)
continue
obj_list.append(obj)
return obj_list
def has_cib_changed(self):
return self.mkobj_list("xml","changed") or self.remove_queue
def check_children(self,children_ids,obj_type,obj_id):
# check prerequisites:
# a) all children must exist
# b) no child may have other parent than me
# (or should we steal children?)
for child_id in children_ids:
if not self.check_child(child_id,obj_type,obj_id):
return False
return True
def check_child(self,child_id,obj_type,obj_id):
'Check if child exists and obj_id is (or may be) its parent.'
child = self.find_object(child_id)
if not child:
common_err("%s does not exist"%child_id)
return False
if child.parent and child.parent.obj_id != obj_id:
common_err("%s already in use at %s"%(child_id,child.parent.obj_id))
return False
if obj_type == "group" and child.obj_type != "primitive":
common_err("a group may contain only primitives; %s is %s"%(child_id,child.obj_type))
return False
if child.obj_type != "primitive" and child.obj_type != "group":
common_err("%s may contain a primitive or a group; %s is %s"%(obj_type,child_id,child.obj_type))
return False
return True
def create_object(self,*args):
self.create_from_cli(parse_cli(args))
def set_property_cli(self,cli_list):
head_pl = cli_list[0]
obj_type = head_pl[0]
pset_id = find_value(head_pl[1],"$id")
if pset_id:
head_pl[1].remove(["$id",pset_id])
else:
pset_id = default_cluster_property_set
obj = self.find_object(pset_id)
if not obj:
obj = self.new_object(obj_type,pset_id)
self.topnode[obj.parent_type].appendChild(obj.node)
obj.origin = "user"
for n,v in head_pl[1]:
set_property(obj.node,n,v)
obj.updated = True
def create_from_cli(self,cli):
'Create a new cib object from the cli representation.'
if type(cli) == type('') or type(cli) == type(u''):
cli_list = parse_cli(cli)
else:
cli_list = cli
if not cli_list:
return
head = cli_list[0]
obj_type = head[0]
if obj_type == "property":
self.set_property_cli(cli_list)
return
obj_id = find_value(head[1],"id")
c_ids = find_value(head[1],"$children")
if c_ids:
if not self.check_children(c_ids,obj_type,obj_id):
return
obj = self.new_object(obj_type,obj_id)
if not obj:
return
obj.node = obj.cli2node(cli_list)
self.topnode[obj.parent_type].appendChild(obj.node)
if c_ids:
self.adjust_children(obj,c_ids)
obj.origin = "user"
for child in obj.children:
if child.origin == "cib":
self.add_to_remove_queue(child)
self.cib_objects.append(obj)
def adjust_children(self,obj,children_ids):
'''
All stuff children related: manage the nodes of children,
update the list of children for the parent, update
parents in the children.
'''
new_children = []
for child_id in children_ids:
new_children.append(self.find_object(child_id))
self._relink_orphans(obj,new_children)
obj.children = new_children
self._update_children(obj)
def _relink_child(self,obj):
'Relink a child to the top node.'
obj.node.parentNode.removeChild(obj.node)
self.topnode[obj.parent_type].appendChild(obj.node)
obj.parent = None
def _update_children(self,obj):
'''For composite objects: update all children nodes.
'''
# unlink all and find them in the new node
for child in obj.children:
oldnode = child.node
child.node = obj.find_child_in_node(child)
if child.children: # and children of children
self._update_children(child)
oldnode.parentNode.removeChild(oldnode)
oldnode.unlink()
child.parent = obj
def _relink_orphans(self,obj,new_children):
"New orphans move to the top level for the object type."
for child in obj.children:
if child not in new_children:
self._relink_child(child)
def create_from_node(self,node):
'Create a new cib object from a document node.'
if not node:
return
obj = self.new_object(node.tagName,node.getAttribute("id"))
if not obj:
return
node = self.doc.importNode(node,1)
self.topnode[obj.parent_type].appendChild(node)
if not obj.save_xml(node):
obj.nocli = True
self.update_links(obj)
obj.origin = "user"
self.cib_objects.append(obj)
def cib_objects_string(self):
l = []
for obj in self.cib_objects:
l.append(obj.obj_string())
return ' '.join(l)
def _remove_obj(self,obj):
"Remove a cib object and its children."
# remove children first
# can't remove them here from obj.children!
global id_store
for child in obj.children:
self._remove_obj(child)
id_store.remove_xml(obj.node)
obj.node.parentNode.removeChild(obj.node)
obj.node.unlink()
self.cib_objects.remove(obj)
def related_constraints(self,obj):
if not is_resource(obj.node):
return []
c_list = []
for obj2 in self.cib_objects:
if not is_constraint(obj2.node):
continue
if rsc_constraint(obj.obj_id,obj2.node):
c_list.append(obj2)
return c_list
def add_to_remove_queue(self,obj):
self.remove_queue.append(obj)
self.remove_queue += self.related_constraints(obj)
#for obj in self.remove_queue: print obj.obj_string()
def delete(self,*args):
'Delete a cib object.'
l = []
for obj_id in args:
obj = self.find_object(obj_id)
if not obj:
common_err("object %s not found"%obj_id)
continue
l.append(obj)
for obj in l:
# remove the parent for which this object is the
# only child: empty containers are of no use and not
# allowed
while obj.parent:
if len(obj.parent.children) > 1:
break
else:
obj = obj.parent
# recurse in another function; otherwise the previous
# code would make an infinite loop
self._remove_obj(obj)
#print "remove",obj.obj_string()
obj.children = [] # children are gone
if obj.origin == "cib":
self.add_to_remove_queue(obj)
def erase(self):
"Remove all cib objects."
# find only topmost objects
for obj in [obj for obj in self.cib_objects if not obj.parent]:
self.delete(obj.obj_id)
if self.cib_objects:
common_err("strange, but these objects remained:")
for obj in self.cib_objects:
print obj.obj_string()
self.cib_objects = []
class TopLevel(object):
'''
The top level.
'''
help_table = {
".": ("","""This is the CRM command line interface program."""),
"cib": ("manage shadow CIBs", """
A shadow CIB is a regular cluster configuration which is kept in
a file. The CRM and the CRM tools may manage a shadow CIB in the
same way as the live CIB (i.e. the current cluster configuration).
A shadow CIB may be applied to the cluster in one step.
"""),
"resource": ("resources management", """
Everything related to resources management is available at this
level. Most commands are implemented using the crm_resource(8)
program.
"""),
"node": ("nodes management", """
A few node related tasks such as node standby are implemented
here.
"""),
"options": ("user preferences", """
Several user preferences are available. Note that it is possible
to save the preferences to a startup file.
"""),
"configure": ("CRM cluster configuration", """
The configuration level.
Note that you can change the working CIB at the cib level. It is
advisable to configure shadow CIBs and then commit them to the
cluster.
"""),
"quit": ("exit the program", ""),
"help": ("show help", ""),
"end": ("go back one level", ""),
}
def __init__(self):
self.cmd_table = {
'cib': CibShadow,
'resource': RscMgmt,
'configure': CibConfig,
'node': NodeMgmt,
'options': CliOptions,
'status': (self.status,(0,0),0),
'help': (self.help,(0,1),0),
'end': (cmd_end,(0,0),0),
'exit': (cmd_exit,(0,0),0),
'quit': (cmd_exit,(0,0),0),
}
def help(self,cmd,topic = ''):
cmd_help(self.help_table,topic)
def status(self,cmd):
ext_cmd("crm_mon -1")
+def attr_cmds(delimiter = False):
+ if delimiter:
+ return ' '
+ return ["delete","set","show","s-1","s-2"]
+def rsc_list(delimiter = False):
+ if delimiter:
+ return ' '
+ cmd = "%s -o %s" % (cib_dump,"resources")
+ s = '\n'.join(os_system(cmd))
+ try: doc = xml.dom.minidom.parseString(s)
+ except xml.parsers.expat.ExpatError,msg:
+ cib_parse_err(msg)
+ return []
+ nodes = get_interesting_nodes(doc,[])
+ return [x.getAttribute("id") for x in nodes if is_resource(x)]
+def skills_list(delimiter = False):
+ if delimiter:
+ return ' '
+ return CliOptions.skill_levels.keys()
+def property_list(delimiter = False):
+ if delimiter:
+ return '='
+ if wcache.is_cached("property_list"):
+ return wcache.cached("property_list")
+ f = os.popen("@hb_libdir@/pengine metadata")
+ doc = xml.dom.minidom.parse(f)
+ f.close()
+ l = []
+ for node in doc.getElementsByTagName("parameter"):
+ p = node.getAttribute("name")
+ if p:
+ l.append(p)
+ doc.unlink()
+ return wcache.cache("property_list",l)
+
def mk_completion_tab(cmd_class,ctab = {}):
obj = cmd_class()
cmd_table = obj.cmd_table
for key,value in cmd_table.items():
if type(value) == type(object):
ctab[key] = {}
mk_completion_tab(value,ctab[key])
else:
ctab[key] = None
try: ctab[key] = value[3]
except: pass
def lookup_dynamic(fun_list,words):
if not fun_list:
return []
- if len(words) == 0:
- return [x+' ' for x in fun_list[0]()]
- elif len(words) == 1 and readline.get_line_buffer()[-1] != ' ':
- return [x+' ' for x in fun_list[0]() if x.startswith(words[0])]
- else:
- wordlist = fun_list[0]()
- if words[0] in wordlist:
- return lookup_dynamic(fun_list[1:],words[1:])
+ f = fun_list[0]
+ w = words[0]
+ wordlist = f()
+ delimiter = f(1)
+ if len(words) == 1:
+ return [x+delimiter for x in wordlist if x.startswith(w)]
+ elif w in wordlist:
+ return lookup_dynamic(fun_list[1:],words[1:])
return []
def lookup_words(ctab,words):
if not ctab:
return []
if type(ctab) == type(()):
return lookup_dynamic(ctab,words)
- if len(words) == 0:
- return [x+' ' for x in ctab]
- elif len(words) == 1 and readline.get_line_buffer()[-1] != ' ':
+ 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 completer(txt,state):
global levels
words = readline.get_line_buffer().split()
+ if not words or readline.get_line_buffer()[-1] == ' ':
+ words.append('')
matched = lookup_words(levels.completion_tab,words)
- if matched:
- matched.append(None)
+ matched.append(None)
return matched[state]
+wcache = WCache()
err_buf = ErrorBuffer()
user_prefs = UserPrefs()
id_store = IdMgmt()
cib_factory = CibFactory()
def load_rc(rcfile):
try: f = open(rcfile)
except: return
save_stdin = sys.stdin
sys.stdin = f
while True:
inp = multi_input()
if inp == None:
break
parse_line(levels,shlex.split(inp))
f.close()
sys.stdin = save_stdin
def multi_input(prompt = ''):
"""
Get input from user
Allow multiple lines using a continuation character
"""
line = []
while True:
try:
text = raw_input(prompt)
except EOFError:
return None
stripped = text.strip()
if stripped.endswith('\\'):
stripped = stripped.rstrip('\\')
line.append(stripped)
prompt = '> '
else:
line.append(stripped)
break
return ''.join(line)
class Levels(object):
'''
Keep track of levels and prompts.
'''
def __init__(self,start_level):
self._marker = 0
self.level_stack = []
self.comp_stack = []
self.current_level = start_level()
self.parse_root = self.current_level.cmd_table
self.prompts = []
self.completion_tab = {}
mk_completion_tab(start_level,self.completion_tab)
def getprompt(self):
return ' '.join(self.prompts)
def mark(self):
self._marker = len(self.level_stack)
def release(self):
while len(self.level_stack) > self._marker:
self.droplevel()
def new_level(self,level_obj,token):
self.level_stack.append(self.current_level)
self.comp_stack.append(self.completion_tab)
self.prompts.append(token)
self.current_level = level_obj()
self.parse_root = self.current_level.cmd_table
self.completion_tab = self.completion_tab[token]
def droplevel(self):
if self.level_stack:
try: self.current_level.end_game()
except: pass
self.current_level = self.level_stack.pop()
self.completion_tab = self.comp_stack.pop()
self.parse_root = self.current_level.cmd_table
self.prompts.pop()
def check_args(args,argsdim):
if not argsdim: return True
if len(argsdim) == 1:
min = argsdim[0]
return len(args) >= min
else:
min,max = argsdim
return len(args) >= min and len(args) <= max
#
# Note on parsing
#
# Parsing tables are python dictionaries.
#
# Keywords are used as keys and the corresponding values are
# lists (actually tuples, since they should be read-only) or
# classes. In the former case, the keyword is a terminal and
# in the latter, a new object for the class is created. The class
# must have the cmd_table variable.
#
# The list has the following content:
#
# function: a function to handle this command
# numargs_list: number of minimum/maximum arguments; for example,
# (0,1) means one optional argument, (1,1) one required; if the
# list is empty then the function will parse arguments itself
# required minimum skill level: operator, administrator, expert
# (encoded as a small integer from 0 to 2)
#
def show_usage(cmd):
p = None
try: p = cmd.__doc__
except: pass
if p:
print p
else:
syntax_err(cmd.__name__)
def parse_line(lvl,s):
if not s: return
if s[0].startswith('#'): return
lvl.mark()
pt = lvl.parse_root
cmd = None
for i in range(len(s)):
token = s[i]
if token in pt:
if type(pt[token]) == type(object):
lvl.new_level(pt[token],token)
pt = lvl.parse_root # move to the next level
else:
cmd = pt[token] # terminal symbol
break # and stop parsing
else:
syntax_err(s[i:])
lvl.release()
return
if cmd: # found a terminal symbol
if not user_prefs.check_skill_level(cmd[2]):
skill_err(s[i])
return
args = s[i+1:]
if not check_args(args,cmd[1]):
show_usage(cmd[0])
return
args = s[i:]
d = lambda: cmd[0](*args)
rv = d() # execute the command
lvl.release()
-levels = Levels(TopLevel)
# three modes: interactive (no args supplied), batch (input from
# a file), half-interactive (args supplied, but not batch)
interactive = True
batch = False
inp_file = ''
prompt = ''
cib_in_use = os.getenv(CibShadow().envvar)
def cib_prompt():
return cib_in_use or "live"
def usage():
print """
usage:
crm
crm args
crm [-f file]
Use crm without arguments for an interactive session.
Supply one or more arguments for a "single-shot" use.
Specify with -f a file which contains a script. Use '-' for
standard input.
Examples:
# crm -f stopapp2.cli
# crm -f - < stopapp2.cli
# crm resource stop global_www
# crm status
"""
sys.exit()
def prereqs():
proglist = "cibadmin crm_resource crm_attribute crm_mon crm_standby crm_failcount"
for prog in proglist.split():
if not is_program(prog):
no_prog_err(prog)
sys.exit(1)
prereqs()
hist_file = os.environ.get('HOME')+"/.crm_history"
rc_file = os.environ.get('HOME')+"/.crm.rc"
readline.set_history_length(100)
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
+readline.set_completer_delims(\
+ readline.get_completer_delims().replace('-',''))
try: readline.read_history_file(hist_file)
except: pass
+build_completions = True
+levels = Levels(TopLevel)
+build_completions = False
load_rc(rc_file)
argc = len(sys.argv)
if argc > 1:
interactive = False
if sys.argv[1] == "-f":
batch = True
if argc != 3:
usage()
inp_file = sys.argv[2]
elif sys.argv[1] == "-h":
usage()
else:
args = sys.argv[1:]
parse_line(levels,args)
sys.exit()
if inp_file == "-":
pass
elif inp_file:
try:
f = open(inp_file)
except IOError, msg:
common_err(msg)
usage()
sys.stdin = f
while True:
if interactive:
prompt = "crm(%s)%s# " % (cib_prompt(),levels.getprompt())
inp = multi_input(prompt)
if inp == None:
cmd_exit("eof")
parse_line(levels,shlex.split(inp))
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jan 25, 12:29 PM (13 h, 24 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1322568
Default Alt Text
(99 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment