diff --git a/lib/cli.py b/lib/cli.py index e9bec2aed0eed2e5109d4affedba87299cc00957..d225bafed2ab9adce84ccd2f985a9bd9b50f6c47 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -32,6 +32,7 @@ from ganeti import logger from ganeti import errors from ganeti import mcpu from ganeti import constants +from ganeti import opcodes from optparse import (OptionParser, make_option, TitledHelpFormatter, Option, OptionValueError, SUPPRESS_HELP) @@ -39,7 +40,81 @@ from optparse import (OptionParser, make_option, TitledHelpFormatter, __all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode", "cli_option", "GenerateTable", "AskUser", "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE", - "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT"] + "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT", + "ListTags", "AddTags", "RemoveTags", + ] + + +def _ExtractTagsObject(opts, args): + """Extract the tag type object. + + Note that this function will modify its args parameter. + + """ + if not hasattr(opts, "tag_type"): + raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject") + kind = opts.tag_type + if kind == constants.TAG_CLUSTER: + retval = kind, kind + elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE: + if not args: + raise errors.OpPrereq("no arguments passed to the command") + name = args.pop(0) + retval = kind, name + else: + raise errors.ProgrammerError("Unhandled tag type '%s'" % kind) + return retval + + +def ListTags(opts, args): + """List the tags on a given object. + + This is a generic implementation that knows how to deal with all + three cases of tag objects (cluster, node, instance). The opts + argument is expected to contain a tag_type field denoting what + object type we work on. + + """ + kind, name = _ExtractTagsObject(opts, args) + op = opcodes.OpGetTags(kind=kind, name=name) + result = SubmitOpCode(op) + result = list(result) + result.sort() + for tag in result: + print tag + + +def AddTags(opts, args): + """Add tags on a given object. + + This is a generic implementation that knows how to deal with all + three cases of tag objects (cluster, node, instance). The opts + argument is expected to contain a tag_type field denoting what + object type we work on. + + """ + kind, name = _ExtractTagsObject(opts, args) + if not args: + raise errors.OpPrereqError("No tags to be added") + op = opcodes.OpAddTags(kind=kind, name=name, tags=args) + SubmitOpCode(op) + + +def RemoveTags(opts, args): + """Remove tags from a given object. + + This is a generic implementation that knows how to deal with all + three cases of tag objects (cluster, node, instance). The opts + argument is expected to contain a tag_type field denoting what + object type we work on. + + """ + kind, name = _ExtractTagsObject(opts, args) + if not args: + raise errors.OpPrereqError("No tags to be removed") + op = opcodes.OpDelTags(kind=kind, name=name, tags=args) + SubmitOpCode(op) + DEBUG_OPT = make_option("-d", "--debug", default=False, action="store_true", @@ -68,7 +143,6 @@ FORCE_OPT = make_option("-f", "--force", dest="force", action="store_true", _LOCK_OPT = make_option("--lock-retries", default=None, type="int", help=SUPPRESS_HELP) - def ARGS_FIXED(val): """Macro-like function denoting a fixed number of arguments""" return -val diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster index 97ac0e8d332b04bf7a2e6035de8e0c635da229df..6361c3bcdd3f1cb9668b66a966ba28da6ff7cf3b 100755 --- a/scripts/gnt-cluster +++ b/scripts/gnt-cluster @@ -263,7 +263,13 @@ commands = { "Runs a command on all (or only some) nodes"), 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT], "", "Show cluster configuration"), + 'list-tags': (ListTags, ARGS_NONE, + [DEBUG_OPT], "", "List the tags of the cluster"), + 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT], + "tag...", "Add tags to the cluster"), + 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT], + "tag...", "Remove tags from the cluster"), } if __name__ == '__main__': - sys.exit(GenericMain(commands)) + sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER})) diff --git a/scripts/gnt-instance b/scripts/gnt-instance index 97b1f38d61653db23f49096c290fcf9649282677..488ed3e2b20e8ecfc894b872e0b5c2f85c9a6cc0 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -754,7 +754,14 @@ commands = { 'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT], "<instance>", "Deactivate an instance's disks"), + 'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT], + "<node_name>", "List the tags of the given instance"), + 'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT], + "<node_name> tag...", "Add tags to the given instance"), + 'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT], + "<node_name> tag...", "Remove tags from given instance"), } if __name__ == '__main__': - sys.exit(GenericMain(commands)) + sys.exit(GenericMain(commands, + override={"tag_type": constants.TAG_INSTANCE})) diff --git a/scripts/gnt-node b/scripts/gnt-node index bfc9940e1f9c8e8f01b4172d2c126612284cb4b0..c87070b6a355b9750b847931c69d6fbe56f2d367 100755 --- a/scripts/gnt-node +++ b/scripts/gnt-node @@ -26,6 +26,7 @@ from ganeti.cli import * from ganeti import opcodes from ganeti import logger from ganeti import utils +from ganeti import constants def AddNode(opts, args): @@ -180,8 +181,14 @@ commands = { 'volumes': (ListVolumes, ARGS_ANY, [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "[<node_name>...]", "List logical volumes on node(s)"), + 'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT], + "<node_name>", "List the tags of the given node"), + 'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT], + "<node_name> tag...", "Add tags to the given node"), + 'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT], + "<node_name> tag...", "Remove tags from the given node"), } if __name__ == '__main__': - sys.exit(GenericMain(commands)) + sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))