diff --git a/lib/cli.py b/lib/cli.py index e33467b17ac604a6fb2e3e6be0d9d012d0f87628..f46a0a05726fbde8d09f434d55d9126359e2f149 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -54,6 +54,7 @@ __all__ = [ # Command line options "ABSOLUTE_OPT", "ADD_UIDS_OPT", + "ADD_RESERVED_IPS_OPT", "ALLOCATABLE_OPT", "ALLOC_POLICY_OPT", "ALL_OPT", @@ -87,6 +88,8 @@ __all__ = [ "FORCE_FILTER_OPT", "FORCE_OPT", "FORCE_VARIANT_OPT", + "GATEWAY_OPT", + "GATEWAY6_OPT", "GLOBAL_FILEDIR_OPT", "HID_OS_OPT", "GLOBAL_SHARED_FILEDIR_OPT", @@ -111,6 +114,9 @@ __all__ = [ "MC_OPT", "MIGRATION_MODE_OPT", "NET_OPT", + "NETWORK_OPT", + "NETWORK6_OPT", + "NETWORK_TYPE_OPT", "NEW_CLUSTER_CERT_OPT", "NEW_CLUSTER_DOMAIN_SECRET_OPT", "NEW_CONFD_HMAC_KEY_OPT", @@ -118,6 +124,7 @@ __all__ = [ "NEW_SECONDARY_OPT", "NEW_SPICE_CERT_OPT", "NIC_PARAMS_OPT", + "NOCONFLICTSCHECK_OPT", "NODE_FORCE_JOIN_OPT", "NODE_LIST_OPT", "NODE_PLACEMENT_OPT", @@ -160,6 +167,7 @@ __all__ = [ "READD_OPT", "REBOOT_TYPE_OPT", "REMOVE_INSTANCE_OPT", + "REMOVE_RESERVED_IPS_OPT", "REMOVE_UIDS_OPT", "RESERVED_LVS_OPT", "RUNTIME_MEM_OPT", @@ -236,11 +244,13 @@ __all__ = [ "ARGS_MANY_INSTANCES", "ARGS_MANY_NODES", "ARGS_MANY_GROUPS", + "ARGS_MANY_NETWORKS", "ARGS_NONE", "ARGS_ONE_INSTANCE", "ARGS_ONE_NODE", "ARGS_ONE_GROUP", "ARGS_ONE_OS", + "ARGS_ONE_NETWORK", "ArgChoice", "ArgCommand", "ArgFile", @@ -248,6 +258,7 @@ __all__ = [ "ArgHost", "ArgInstance", "ArgJobId", + "ArgNetwork", "ArgNode", "ArgOs", "ArgSuggest", @@ -258,6 +269,7 @@ __all__ = [ "OPT_COMPL_ONE_INSTANCE", "OPT_COMPL_ONE_NODE", "OPT_COMPL_ONE_NODEGROUP", + "OPT_COMPL_ONE_NETWORK", "OPT_COMPL_ONE_OS", "cli_option", "SplitNodeOption", @@ -356,6 +368,11 @@ class ArgNode(_Argument): """ +class ArgNetwork(_Argument): + """Network argument. + + """ + class ArgGroup(_Argument): """Node group argument. @@ -394,9 +411,11 @@ class ArgOs(_Argument): ARGS_NONE = [] ARGS_MANY_INSTANCES = [ArgInstance()] +ARGS_MANY_NETWORKS = [ArgNetwork()] ARGS_MANY_NODES = [ArgNode()] ARGS_MANY_GROUPS = [ArgGroup()] ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)] +ARGS_ONE_NETWORK = [ArgNetwork(min=1, max=1)] ARGS_ONE_NODE = [ArgNode(min=1, max=1)] # TODO ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)] @@ -640,8 +659,9 @@ def check_maybefloat(option, opt, value): # pylint: disable=W0613 OPT_COMPL_ONE_INSTANCE, OPT_COMPL_ONE_OS, OPT_COMPL_ONE_IALLOCATOR, + OPT_COMPL_ONE_NETWORK, OPT_COMPL_INST_ADD_NODES, - OPT_COMPL_ONE_NODEGROUP) = range(100, 107) + OPT_COMPL_ONE_NODEGROUP) = range(100, 108) OPT_COMPL_ALL = frozenset([ OPT_COMPL_MANY_NODES, @@ -649,6 +669,7 @@ OPT_COMPL_ALL = frozenset([ OPT_COMPL_ONE_INSTANCE, OPT_COMPL_ONE_OS, OPT_COMPL_ONE_IALLOCATOR, + OPT_COMPL_ONE_NETWORK, OPT_COMPL_INST_ADD_NODES, OPT_COMPL_ONE_NODEGROUP, ]) @@ -1457,6 +1478,44 @@ ABSOLUTE_OPT = cli_option("--absolute", dest="absolute", help="Marks the grow as absolute instead of the" " (default) relative mode") +NETWORK_OPT = cli_option("--network", + action="store", default=None, dest="network", + help="IP network in CIDR notation") + +GATEWAY_OPT = cli_option("--gateway", + action="store", default=None, dest="gateway", + help="IP address of the router (gateway)") + +ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips", + action="store", default=None, + dest="add_reserved_ips", + help="Comma-separated list of" + " reserved IPs to add") + +REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips", + action="store", default=None, + dest="remove_reserved_ips", + help="Comma-delimited list of" + " reserved IPs to remove") + +NETWORK_TYPE_OPT = cli_option("--network-type", + action="store", default=None, dest="network_type", + help="Network type: private, public, None") + +NETWORK6_OPT = cli_option("--network6", + action="store", default=None, dest="network6", + help="IP network in CIDR notation") + +GATEWAY6_OPT = cli_option("--gateway6", + action="store", default=None, dest="gateway6", + help="IP6 address of the router (gateway)") + +NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check", + dest="conflicts_check", + default=True, + action="store_false", + help="Don't check for conflicting IPs") + #: Options provided by all commands COMMON_OPTS = [DEBUG_OPT] @@ -1473,6 +1532,7 @@ COMMON_CREATE_OPTS = [ NET_OPT, NODE_PLACEMENT_OPT, NOIPCHECK_OPT, + NOCONFLICTSCHECK_OPT, NONAMECHECK_OPT, NONICS_OPT, NWSYNC_OPT, diff --git a/lib/client/gnt_instance.py b/lib/client/gnt_instance.py index 8b6be02c614e8777dee73fc5108ad7c6ac61d138..4a9458dab7dd7aeebaecd2d555f7b6f1fc9d8eb8 100644 --- a/lib/client/gnt_instance.py +++ b/lib/client/gnt_instance.py @@ -1338,6 +1338,7 @@ def SetInstanceParams(opts, args): force=opts.force, wait_for_sync=opts.wait_for_sync, offline=offline, + conflicts_check=opts.conflicts_check, ignore_ipolicy=opts.ignore_ipolicy) # even if here we process the result, we allow submit only @@ -1527,7 +1528,8 @@ commands = { [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT, DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT, OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT, - ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT], + ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT, + NOCONFLICTSCHECK_OPT], "<instance>", "Alters the parameters of an instance"), "shutdown": ( GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()], diff --git a/lib/client/gnt_network.py b/lib/client/gnt_network.py new file mode 100644 index 0000000000000000000000000000000000000000..bd0fe833502a4e22d1de990ba6854dedae665094 --- /dev/null +++ b/lib/client/gnt_network.py @@ -0,0 +1,328 @@ +# +# + +# Copyright (C) 2011 Google Inc. +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +"""IP pool related commands""" + +# pylint: disable-msg=W0401,W0614 +# W0401: Wildcard import ganeti.cli +# W0614: Unused import %s from wildcard import (since we need cli) + +from ganeti.cli import * +from ganeti import constants +from ganeti import opcodes +from ganeti import utils +from textwrap import wrap + + +#: default list of fields for L{ListNetworks} +_LIST_DEF_FIELDS = ["name", "network", "gateway", "group_cnt", "group_list"] + + +def _HandleReservedIPs(ips): + if ips is not None: + if ips == "": + return [] + else: + return utils.UnescapeAndSplit(ips, sep=",") + return None + +def AddNetwork(opts, args): + """Add a network to the cluster. + + @param opts: the command line options selected by the user + @type args: list + @param args: a list of length 1 with the network name to create + @rtype: int + @return: the desired exit code + + """ + (network_name, ) = args + + op = opcodes.OpNetworkAdd(network_name=network_name, + gateway=opts.gateway, + network=opts.network, + gateway6=opts.gateway6, + network6=opts.network6, + mac_prefix=opts.mac_prefix, + network_type=opts.network_type, + add_reserved_ips=_HandleReservedIPs(opts.add_reserved_ips)) + SubmitOpCode(op, opts=opts) + + +def MapNetwork(opts, args): + """Map a network to a node group. + + @param opts: the command line options selected by the user + @type args: list + @param args: a list of length 3 with network, nodegroup, mode, physlink + @rtype: int + @return: the desired exit code + + """ + network = args[0] + groups = args[1] + mode = args[2] + link = args[3] + + #TODO: allow comma separated group names + if groups == 'all': + cl = GetClient() + (groups, ) = cl.QueryGroups([], ['name'], False) + else: + groups = [groups] + + for group in groups: + op = opcodes.OpNetworkConnect(group_name=group, + network_name=network, + network_mode=mode, + network_link=link, + conflicts_check=opts.conflicts_check) + SubmitOpCode(op, opts=opts) + + +def UnmapNetwork(opts, args): + """Unmap a network from a node group. + + @param opts: the command line options selected by the user + @type args: list + @param args: a list of length 3 with network, nodegorup + @rtype: int + @return: the desired exit code + + """ + network = args[0] + groups = args[1] + + #TODO: allow comma separated group names + if groups == 'all': + cl = GetClient() + (groups, ) = cl.QueryGroups([], ['name'], False) + else: + groups = [groups] + + for group in groups: + op = opcodes.OpNetworkDisconnect(group_name=group, + network_name=network, + conflicts_check=opts.conflicts_check) + SubmitOpCode(op, opts=opts) + + +def ListNetworks(opts, args): + """List Ip pools and their properties. + + @param opts: the command line options selected by the user + @type args: list + @param args: networks to list, or empty for all + @rtype: int + @return: the desired exit code + + """ + desired_fields = ParseFields(opts.output, _LIST_DEF_FIELDS) + fmtoverride = { + "group_list": (",".join, False), + "inst_list": (",".join, False), + } + + return GenericList(constants.QR_NETWORK, desired_fields, args, None, + opts.separator, not opts.no_headers, + verbose=opts.verbose, format_override=fmtoverride) + + +def ListNetworkFields(opts, args): + """List network fields. + + @param opts: the command line options selected by the user + @type args: list + @param args: fields to list, or empty for all + @rtype: int + @return: the desired exit code + + """ + return GenericListFields(constants.QR_NETWORK, args, opts.separator, + not opts.no_headers) + + +def ShowNetworkConfig(opts, args): + """Show network information. + + @param opts: the command line options selected by the user + @type args: list + @param args: should either be an empty list, in which case + we show information about all nodes, or should contain + a list of networks (names or UUIDs) to be queried for information + @rtype: int + @return: the desired exit code + + """ + cl = GetClient() + result = cl.QueryNetworks(fields=["name", "network", "gateway", + "network6", "gateway6", + "mac_prefix", "network_type", + "free_count", "reserved_count", + "map", "group_list", "inst_list", + "external_reservations"], + names=args, use_locking=False) + + for (name, network, gateway, network6, gateway6, + mac_prefix, network_type, free_count, reserved_count, + map, group_list, instances, ext_res) in result: + size = free_count + reserved_count + ToStdout("Network name: %s", name) + ToStdout(" subnet: %s", network) + ToStdout(" gateway: %s", gateway) + ToStdout(" subnet6: %s", network6) + ToStdout(" gateway6: %s", gateway6) + ToStdout(" mac prefix: %s", mac_prefix) + ToStdout(" type: %s", network_type) + ToStdout(" size: %d", size) + ToStdout(" free: %d (%.2f%%)", free_count, + 100 * float(free_count)/float(size)) + ToStdout(" usage map:") + idx = 0 + for line in wrap(map, width=64): + ToStdout(" %s %s %d", str(idx).rjust(3), line.ljust(64), idx + 63) + idx += 64 + ToStdout(" (X) used (.) free") + + if ext_res: + ToStdout(" externally reserved IPs:") + for line in wrap(ext_res, width=64): + ToStdout(" %s" % line) + + if group_list: + ToStdout(" connected to node groups:") + for group in group_list: + ToStdout(" %s", group) + else: + ToStdout(" not connected to any node group") + + if instances: + ToStdout(" used by %d instances:", len(instances)) + for inst in instances: + ((ips, networks), ) = cl.QueryInstances([inst], + ["nic.ips", "nic.networks"], + use_locking=False) + + l = lambda value: ", ".join(`idx`+":"+str(ip) + for idx, (ip, net) in enumerate(value) + if net == name) + + ToStdout(" %s : %s", inst, l(zip(ips,networks))) + else: + ToStdout(" not used by any instances") + + +def SetNetworkParams(opts, args): + """Modifies an IP address pool's parameters. + + @param opts: the command line options selected by the user + @type args: list + @param args: should contain only one element, the node group name + + @rtype: int + @return: the desired exit code + + """ + + # TODO: add "network": opts.network, + all_changes = { + "gateway": opts.gateway, + "add_reserved_ips": _HandleReservedIPs(opts.add_reserved_ips), + "remove_reserved_ips": _HandleReservedIPs(opts.remove_reserved_ips), + "mac_prefix": opts.mac_prefix, + "network_type": opts.network_type, + "gateway6": opts.gateway6, + "network6": opts.network6, + } + + if all_changes.values().count(None) == len(all_changes): + ToStderr("Please give at least one of the parameters.") + return 1 + + op = opcodes.OpNetworkSetParams(network_name=args[0], + # pylint: disable-msg=W0142 + **all_changes) + + # TODO: add feedback to user, e.g. list the modifications + SubmitOrSend(op, opts) + + +def RemoveNetwork(opts, args): + """Remove an IP address pool from the cluster. + + @param opts: the command line options selected by the user + @type args: list + @param args: a list of length 1 with the id of the IP address pool to remove + @rtype: int + @return: the desired exit code + + """ + (network_name,) = args + op = opcodes.OpNetworkRemove(network_name=network_name, force=opts.force) + SubmitOpCode(op, opts=opts) + + +commands = { + "add": ( + AddNetwork, ARGS_ONE_NETWORK, + [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT, + MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT], + "<network_name>", "Add a new IP network to the cluster"), + "list": ( + ListNetworks, ARGS_MANY_NETWORKS, + [NOHDR_OPT, SEP_OPT, FIELDS_OPT, VERBOSE_OPT], + "[<network_id>...]", + "Lists the IP networks in the cluster. The available fields can be shown" + " using the \"list-fields\" command (see the man page for details)." + " The default list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS)), + "list-fields": ( + ListNetworkFields, [ArgUnknown()], [NOHDR_OPT, SEP_OPT], "[fields...]", + "Lists all available fields for networks"), + "info": ( + ShowNetworkConfig, ARGS_MANY_NETWORKS, [], + "[<network_name>...]", "Show information about the network(s)"), + "modify": ( + SetNetworkParams, ARGS_ONE_NETWORK, + [DRY_RUN_OPT, SUBMIT_OPT, ADD_RESERVED_IPS_OPT, REMOVE_RESERVED_IPS_OPT, + GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT], + "<network_name>", "Alters the parameters of a network"), + "connect": ( + MapNetwork, + [ArgNetwork(min=1, max=1), ArgGroup(min=1, max=1), + ArgUnknown(min=1, max=1), ArgUnknown(min=1, max=1)], + [NOCONFLICTSCHECK_OPT], + "<network_name> <node_group> <mode> <link>", + "Map a given network to the specified node group" + " with given mode and link (netparams)"), + "disconnect": ( + UnmapNetwork, + [ArgNetwork(min=1, max=1), ArgGroup(min=1, max=1)], + [NOCONFLICTSCHECK_OPT], + "<network_name> <node_group>", + "Unmap a given network from a specified node group"), + "remove": ( + RemoveNetwork, ARGS_ONE_NETWORK, [FORCE_OPT, DRY_RUN_OPT], + "[--dry-run] <network_id>", + "Remove an (empty) network from the cluster"), +} + + +def Main(): + return GenericMain(commands)