diff --git a/crm/admin/Makefile.am b/crm/admin/Makefile.am index 09f6c5d812..157e2287ea 100644 --- a/crm/admin/Makefile.am +++ b/crm/admin/Makefile.am @@ -1,116 +1,118 @@ # # Copyright (C) 2004 Andrew Beekhof # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program 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 program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # MAINTAINERCLEANFILES = Makefile.in INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ -I$(top_builddir) -I$(top_srcdir) +halibdir = $(libdir)/@HB_PKG@ hasbindir = $(sbindir) LIBRT = @LIBRT@ AM_CFLAGS = @CFLAGS@ $(CRM_DEBUG_FLAGS) COMMONLIBS = $(CRM_DEBUG_LIBS) \ $(top_builddir)/lib/clplumbing/libplumb.la \ $(top_builddir)/lib/pils/libpils.la \ $(top_builddir)/lib/crm/common/libcrmcommon.la \ $(top_builddir)/lib/crm/cib/libcib.la \ $(top_builddir)/lib/apphb/libapphb.la \ $(top_builddir)/lib/hbclient/libhbclient.la \ $(GLIBLIB) \ $(CURSESLIBS) \ $(LIBRT) -hasbin_SCRIPTS = crm_primitive.py cluster.py crm_utils.py crm_commands.py -## binary progs +halib_PYTHON = crm_primitive.py crm_utils.py crm_commands.py hasbin_PROGRAMS = crmadmin cibadmin ccm_tool crm_diff crm_mon iso8601 \ crm_master crm_standby crm_failcount crm_attribute \ - crm_resource crm_verify crm_uuid + crm_resource crm_verify crm_uuid + +hasbin_SCRIPTS = cluster ## SOURCES #noinst_HEADERS = config.h control.h crmd.h noinst_HEADERS = crmadmin_SOURCES = crmadmin.c crmadmin_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crmadmin_LDADD = $(COMMONLIBS) \ $(top_builddir)/lib/crm/pengine/libpe_status.la crm_uuid_SOURCES = crm_uuid.c crm_uuid_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_uuid_LDADD = $(GLIBLIB) $(top_builddir)/lib/clplumbing/libplumb.la cibadmin_SOURCES = cibadmin.c cibadmin_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' cibadmin_LDADD = $(COMMONLIBS) ccm_tool_SOURCES = ccm_epoche.c ccm_tool_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' ccm_tool_LDADD = $(COMMONLIBS) \ $(top_builddir)/membership/ccm/libccmclient.la crm_diff_SOURCES = xml_diff.c crm_diff_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_diff_LDADD = $(COMMONLIBS) crm_mon_SOURCES = crm_mon.c crm_mon_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_mon_LDADD = $(COMMONLIBS) \ $(top_builddir)/lib/crm/pengine/libpe_status.la # Arguments could be made that this should live in crm/pengine crm_verify_SOURCES = crm_verify.c crm_verify_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_verify_LDADD = $(COMMONLIBS) \ $(top_builddir)/lib/crm/pengine/libpe_status.la \ $(top_builddir)/crm/pengine/libpengine.la crm_master_SOURCES = crm_attribute.c crm_master_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_master_LDADD = $(COMMONLIBS) crm_standby_SOURCES = crm_attribute.c crm_standby_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_standby_LDADD = $(COMMONLIBS) crm_attribute_SOURCES = crm_attribute.c crm_attribute_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_attribute_LDADD = $(COMMONLIBS) crm_failcount_SOURCES = crm_attribute.c crm_failcount_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_failcount_LDADD = $(COMMONLIBS) crm_resource_SOURCES = crm_resource.c crm_resource_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' crm_resource_LDADD = $(COMMONLIBS) \ $(top_builddir)/lib/crm/pengine/libpe_status.la iso8601_SOURCES = test.iso8601.c iso8601_CFLAGS = -DHA_VARLIBDIR='"@HA_VARLIBDIR@"' iso8601_LDADD = $(COMMONLIBS) clean-generic: rm -f *.log *.debug *.xml *~ install-exec-local: uninstall-local: diff --git a/crm/admin/cluster.in b/crm/admin/cluster.in new file mode 100644 index 0000000000..df6c8b4f50 --- /dev/null +++ b/crm/admin/cluster.in @@ -0,0 +1,361 @@ +#!@PYTHON@ +# +# Copyright (c) 2006 Andrew Beekhof +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### + +import os +import sys +import readline +import traceback +from popen2 import Popen3 + +# Module copied from Mercurial + +sys.path.append("@HA_LIBDIR@/heartbeat") +import crm_utils as utl +import crm_commands as crm + + +class ParseError(Exception): + """Exception raised on errors in parsing the command line.""" +class QuotingError(Exception): + """Exception raised on errors in parsing the command line.""" +class UnknownCommand(Exception): + """Exception raised if command is not in the command table.""" +class AmbiguousCommand(Exception): + """Exception raised if command shortcut matches more than one command.""" + +table = { + "^help": ( + crm.help, None, [('v', 'verbose', None, 'extra information')], + "[-v]"), + "^up": (crm.up, None, [], None, "Move up a level in the heirarchy"), + "^crm": (crm.cd_, ["crm"], [], None), + "^nodes": (crm.cd_, ["crm"], [], None), + "^resources": (crm.cd_, ["crm"], [], None), + "^config": (crm.cd_, ["crm"], [], None), + "^list": ( + crm.do_list, ["nodes", "resources"], [('t', 'topic', "", '')], + None), + "^status": ( + crm.do_status, ["nodes", "resources"], [], + "[list of objects]"), + "^debug": (crm.toggle_flag, None, [('f', 'flag', 'debug', '')], None, "Toggle debugging information"), + "^exit": (crm.exit, None, [], "exit"), + "^debugstate": ( + crm.debugstate, None, [], + None, + "Dump information about the internal state of the program"), + } + +global_opt_table = [ + ('q', 'quiet', None, 'suppress output'), + ('v', 'verbose', None, 'enable additional output'), + ('', 'debug', None, 'enable debugging output'), + ('', 'devlog', None, 'enable developmental debugging output'), + ('', 'debugger', None, 'start debugger'), + ('', 'lsprof', None, 'print improved command execution profile'), + ('', 'traceback', None, 'print traceback on exception'), + ('', 'time', None, 'time how long the command takes'), + ('', 'profile', None, 'print command execution profile'), + ('', 'version', None, 'output version information and exit'), + ('i', 'interactive', None, 'run in interactive mode'), + ('h', 'help', None, 'display help and exit'), + ] + +def help_(text): + utl.log_dev("Looking up help text for: %s" % text) + if text == "short": + utl.log_info("cluster.py [global options] [topic [topic...]] [command]") + return + if text: + choice = findpossible(text, None, False) + for key in choice.keys(): + alias, e = choice[key] + text = alias[0] + utl.log_dev("Help text for: %s" % text) + if e: + sub_cmd="" + if len(e) > 4: + utl.log_info("\n"+e[4]+"\n") + possible = findpossible("", text).keys() + utl.log_dev("Possible sub-commands: "+repr(possible)) + possible.remove("up") + possible.remove("help") + possible.remove("exit") + if text in possible: + possible.remove(text) + if possible: + sub_cmd=' ('+'|'.join(possible)+')' + if e[3]: + utl.log_info("Usage: %s %s%s" % (text, e[3], sub_cmd)) + else: + utl.log_info("Usage: %s%s" % (text, sub_cmd)) + if choice: + return; + utl.log_err("No help text available for: %s" % text) + + +# Stolen from Mercurial commands.py + +def findpossible(cmd, topic=None, filter=True): + """ + Return cmd -> (aliases, command table entry) + for each matching command. + Return debug commands (or their aliases) only if no normal command matches. + """ + if not topic: + topic = utl.crm_topic + utl.log_dev("Searching in default topic: %s" % topic) + + utl.log_dev("Looking for completions of %s in %s" % (cmd, topic)) + choice = {} + debugchoice = {} + topicchoice = {} + for e in table.keys(): + t = table[e] + #utl.log_dev("Processing: %s / %s" % (e, repr(t))) + aliases = e.lstrip("^").split("|") + found = None + if "^%s"%topic == e and t[0] == crm.cd_: + #utl.log_dev("Found: topic") + topicchoice[topic] = (aliases, table[e]) + if filter and t[1] and topic not in t[1]: + #utl.log_dev("Skip: filter") + continue + elif cmd in aliases: + #utl.log_dev("Found: alias") + found = cmd + else: + for a in aliases: + if a.startswith(cmd): + #utl.log_dev("Found: alias prefix") + found = a + break + if found is not None: + if aliases[0].startswith("debug"): + debugchoice[found] = (aliases, table[e]) + else: + choice[found] = (aliases, table[e]) + + if not choice and debugchoice: + choice = debugchoice + + if not choice and topicchoice: + choice = topicchoice + + return choice + +def findcmd(cmd): + """Return (aliases, command table entry) for command string.""" + choice = findpossible(cmd) + + if choice.has_key(cmd): + #utl.log_dev("Choice has: %s" % cmd) + return choice[cmd] + + if len(choice) > 1: + clist = choice.keys() + clist.sort() + raise AmbiguousCommand(cmd, clist) + + if choice: + #utl.log_dev("Returning first: %s" % (repr(choice.values()[0]))) + return choice.values()[0] + + raise UnknownCommand(cmd) + +def find_completer(start, i): + choice = findpossible(start) + if not choice: + return None + elif len(choice.keys()) < i: + return None + return choice.keys()[i] + +def parse(args): + options = {} + cmdoptions = {} + + try: + args = utl.fancyopts(args, global_opt_table, options) + except utl.getopt.GetoptError, inst: + raise ParseError(None, inst) + + if args: + cmd, args = args[0], args[1:] + utl.log_dev("Initial Command: %s" % cmd) + aliases, i = findcmd(cmd) + cmd = aliases[0] + utl.log_dev("Found Command: %s" % cmd) + defaults = [] + if defaults: + args = defaults.split() + args + c = list(i[2]) + else: + cmd = None + c = [] + + # combine global options into local + for o in global_opt_table: + c.append((o[0], o[1], options[o[1]], o[3])) + + try: + args = utl.fancyopts(args, c, cmdoptions) + except utl.getopt.GetoptError, inst: + raise ParseError(cmd, inst) + + # separate global options back out + for o in global_opt_table: + n = o[1] + options[n] = cmdoptions[n] + del cmdoptions[n] + + utl.log_dev("args: %s\ncmdoptions: %s" + % (repr(args), repr(cmdoptions))) + return (cmd, cmd and i[0] or None, args, options, cmdoptions) + +def main_loop(args): + cmd = None + if not args: + return 0 + + try: + cmd, func, cmd_args, ignore, cmd_options = parse(args) + + if func == crm.cd_: + cmd_options["topic"] = cmd + + utl.log_dev("Func Command: %s" % cmd) + utl.log_dev("Func Args: %s" % repr(cmd_args)) + utl.log_dev("Func Opts: %s" % repr(cmd_options)) + + if not cmd: + utl.log_dev(repr(args)) + return 0 + d = lambda: func(*cmd_args, **cmd_options) + return d() + + except crm.HelpRequest, inst: + help_(inst.args[0]) + return 0 + + except crm.ReparseRequest: + return main_loop(cmd_args) + + except ParseError, inst: + if inst.args[0]: + utl.log_err("%s: %s\n" % (inst.args[0], inst.args[1])) + help_(inst.args[0]) + else: + utl.log_err("%s\n" % inst.args[1]) + help_('short') + + except AmbiguousCommand, inst: + utl.log_info("%s: command '%s' is ambiguous:\n %s\n" + % (" ".join(utl.topic_stack), inst.args[0], " ".join(inst.args[1]))) + + except UnknownCommand, inst: + utl.log_err("%s: unknown command '%s'\n" % (" ".join(utl.topic_stack), inst.args[0])) + help_(utl.crm_topic) + + except IOError, inst: + if hasattr(inst, "code"): + utl.log_err("abort: %s\n" % inst) + elif hasattr(inst, "reason"): + utl.log_err("abort: error: %s\n" % inst.reason[1]) + elif hasattr(inst, "args"): + utl.log_err("broken pipe\n") + elif getattr(inst, "strerror", None): + if getattr(inst, "filename", None): + utl.log_err("abort: %s - %s\n" % (inst.strerror, inst.filename)) + else: + utl.log_err("abort: %s\n" % inst.strerror) + else: + raise + except OSError, inst: + if hasattr(inst, "filename"): + utl.log_err("abort: %s: %s\n" % (inst.strerror, inst.filename)) + else: + utl.log_err("abort: %s\n" % inst.strerror) + + except TypeError, inst: + # was this an argument error? + tb = traceback.extract_tb(sys.exc_info()[2]) + if len(tb) > 2: # no + raise + utl.log_err((inst, "\n")) + utl.log_err(("%s: invalid arguments\n" % cmd)) + help_(cmd) + raise + except SystemExit, inst: + # Exit gracefully + utl.exit_(inst.code) + + except: + utl.log_err("** unknown exception encountered, details follow\n") + raise + + return -1 + +args = sys.argv[1:] +utl.init_readline(find_completer) +try: + cmd, f_ignore, a_ignore, utl.global_opts, o_ignore = parse(args) +except: + pass + +if len(sys.argv) == 1: + utl.global_opts["interactive"] = 1 +elif not utl.global_opts.has_key("interactive"): + utl.global_opts["interactive"] = 0 + +while True: + rc = 0 + if args: + utl.set_topic(utl.topic_stack[-1]) + rc = main_loop(args) + utl.log_debug("rc: %s" % (repr(rc))) + + if not utl.global_opts["interactive"]: + utl.exit_(rc) + + try: + text = raw_input(" ".join(utl.topic_stack) +" # ") + args = utl.create_argv(text) + + except QuotingError, inst: + if inst.args[1]: + utl.log_err("%s. Found tokens: %s\n" % (inst.args[0], inst.args[1])) + else: + utl.log_err("%s\n" % inst.args[0]) + + except KeyboardInterrupt: + pass + + except EOFError: + utl.exit_(0) + + +