| # |
| # |
| |
| # Copyright (C) 2011, 2012, 2013 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=W0401,W0614 |
| # W0401: Wildcard import ganeti.cli |
| # W0614: Unused import %s from wildcard import (since we need cli) |
| |
| import textwrap |
| import itertools |
| |
| from ganeti.cli import * |
| from ganeti import constants |
| from ganeti import opcodes |
| from ganeti import utils |
| from ganeti import errors |
| |
| |
| #: default list of fields for L{ListNetworks} |
| _LIST_DEF_FIELDS = ["name", "network", "gateway", |
| "mac_prefix", "group_list", "tags"] |
| |
| |
| def _HandleReservedIPs(ips): |
| if ips is None: |
| return None |
| elif not ips: |
| return [] |
| else: |
| return utils.UnescapeAndSplit(ips, sep=",") |
| |
| |
| 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 |
| |
| if opts.network is None: |
| raise errors.OpPrereqError("The --network option must be given", |
| errors.ECODE_INVAL) |
| |
| if opts.tags is not None: |
| tags = opts.tags.split(",") |
| else: |
| tags = [] |
| |
| reserved_ips = _HandleReservedIPs(opts.add_reserved_ips) |
| |
| op = opcodes.OpNetworkAdd(network_name=network_name, |
| gateway=opts.gateway, |
| network=opts.network, |
| gateway6=opts.gateway6, |
| network6=opts.network6, |
| mac_prefix=opts.mac_prefix, |
| add_reserved_ips=reserved_ips, |
| conflicts_check=opts.conflicts_check, |
| tags=tags) |
| SubmitOrSend(op, opts) |
| |
| |
| def _GetDefaultGroups(cl, groups): |
| """Gets list of groups to operate on. |
| |
| If C{groups} doesn't contain groups, a list of all groups in the cluster is |
| returned. |
| |
| @type cl: L{luxi.Client} |
| @type groups: list |
| @rtype: list |
| |
| """ |
| if groups: |
| return groups |
| |
| return list(itertools.chain(*cl.QueryGroups([], ["uuid"], False))) |
| |
| |
| def ConnectNetwork(opts, args): |
| """Map a network to a node group. |
| |
| @param opts: the command line options selected by the user |
| @type args: list |
| @param args: Network, mode, physlink and node groups |
| @rtype: int |
| @return: the desired exit code |
| |
| """ |
| cl = GetClient() |
| |
| (network, mode, link) = args[:3] |
| groups = _GetDefaultGroups(cl, args[3:]) |
| |
| # TODO: Change logic to support "--submit" |
| 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, cl=cl) |
| |
| |
| def DisconnectNetwork(opts, args): |
| """Unmap a network from a node group. |
| |
| @param opts: the command line options selected by the user |
| @type args: list |
| @param args: Network and node groups |
| @rtype: int |
| @return: the desired exit code |
| |
| """ |
| cl = GetClient() |
| |
| (network, ) = args[:1] |
| groups = _GetDefaultGroups(cl, args[1:]) |
| |
| # TODO: Change logic to support "--submit" |
| for group in groups: |
| op = opcodes.OpNetworkDisconnect(group_name=group, |
| network_name=network) |
| SubmitOpCode(op, opts=opts, cl=cl) |
| |
| |
| 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": |
| (lambda data: utils.CommaJoin("%s (%s, %s)" % (name, mode, link) |
| for (name, mode, link) in data), |
| False), |
| "inst_list": (",".join, False), |
| "tags": (",".join, False), |
| } |
| |
| cl = GetClient(query=True) |
| return GenericList(constants.QR_NETWORK, desired_fields, args, None, |
| opts.separator, not opts.no_headers, |
| verbose=opts.verbose, format_override=fmtoverride, |
| cl=cl) |
| |
| |
| 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 |
| |
| """ |
| cl = GetClient(query=True) |
| |
| return GenericListFields(constants.QR_NETWORK, args, opts.separator, |
| not opts.no_headers, cl=cl) |
| |
| |
| def ShowNetworkConfig(_, args): |
| """Show network information. |
| |
| @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", |
| "free_count", "reserved_count", |
| "map", "group_list", "inst_list", |
| "external_reservations", |
| "serial_no", "uuid"], |
| names=args, use_locking=False) |
| |
| for (name, network, gateway, network6, gateway6, |
| mac_prefix, free_count, reserved_count, |
| mapping, group_list, instances, ext_res, serial, uuid) in result: |
| size = free_count + reserved_count |
| ToStdout("Network name: %s", name) |
| ToStdout("UUID: %s", uuid) |
| ToStdout("Serial number: %d", serial) |
| ToStdout(" Subnet: %s", network) |
| ToStdout(" Gateway: %s", gateway) |
| ToStdout(" IPv6 Subnet: %s", network6) |
| ToStdout(" IPv6 Gateway: %s", gateway6) |
| ToStdout(" Mac Prefix: %s", mac_prefix) |
| ToStdout(" Size: %d", size) |
| ToStdout(" Free: %d (%.2f%%)", free_count, |
| 100 * float(free_count) / float(size)) |
| ToStdout(" Usage map:") |
| idx = 0 |
| for line in textwrap.wrap(mapping, 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 textwrap.wrap(ext_res, width=64): |
| ToStdout(" %s" % line) |
| |
| if group_list: |
| ToStdout(" connected to node groups:") |
| for group, nic_mode, nic_link in group_list: |
| ToStdout(" %s (%s on %s)", group, nic_mode, nic_link) |
| else: |
| ToStdout(" not connected to any node group") |
| |
| if instances: |
| idata = cl.QueryInstances([], ["uuid", "name"], False) |
| uuid2name = dict(idata) |
| |
| ToStdout(" used by %d instances:", len(instances)) |
| for inst in instances: |
| name = uuid2name[inst] |
| ((ips, networks), ) = cl.QueryInstances([name], |
| ["nic.ips", "nic.networks"], |
| use_locking=False) |
| |
| l = lambda value: ", ".join(str(idx) + ":" + str(ip) |
| for idx, (ip, net) in enumerate(value) |
| if net == uuid) |
| |
| ToStdout(" %s: %s", name, 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, |
| "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 |
| |
| # pylint: disable=W0142 |
| op = opcodes.OpNetworkSetParams(network_name=args[0], **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) |
| SubmitOrSend(op, opts) |
| |
| |
| commands = { |
| "add": ( |
| AddNetwork, ARGS_ONE_NETWORK, |
| [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT, |
| MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT, |
| NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT] + SUBMIT_OPTS, |
| "<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_OPTS + |
| [ADD_RESERVED_IPS_OPT, |
| REMOVE_RESERVED_IPS_OPT, GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK6_OPT, |
| GATEWAY6_OPT, PRIORITY_OPT], |
| "<network_name>", "Alters the parameters of a network"), |
| "connect": ( |
| ConnectNetwork, |
| [ArgNetwork(min=1, max=1), |
| ArgChoice(min=1, max=1, choices=constants.NIC_VALID_MODES), |
| ArgUnknown(min=1, max=1), |
| ArgGroup()], |
| [NOCONFLICTSCHECK_OPT, PRIORITY_OPT], |
| "<network_name> <mode> <link> [<node_group>...]", |
| "Map a given network to the specified node group" |
| " with given mode and link (netparams)"), |
| "disconnect": ( |
| DisconnectNetwork, |
| [ArgNetwork(min=1, max=1), ArgGroup()], |
| [PRIORITY_OPT], |
| "<network_name> [<node_group>...]", |
| "Unmap a given network from a specified node group"), |
| "remove": ( |
| RemoveNetwork, ARGS_ONE_NETWORK, |
| [FORCE_OPT, DRY_RUN_OPT] + SUBMIT_OPTS + [PRIORITY_OPT], |
| "[--dry-run] <network_id>", |
| "Remove an (empty) network from the cluster"), |
| "list-tags": ( |
| ListTags, ARGS_ONE_NETWORK, [], |
| "<network_name>", "List the tags of the given network"), |
| "add-tags": ( |
| AddTags, [ArgNetwork(min=1, max=1), ArgUnknown()], |
| [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS, |
| "<network_name> tag...", "Add tags to the given network"), |
| "remove-tags": ( |
| RemoveTags, [ArgNetwork(min=1, max=1), ArgUnknown()], |
| [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS, |
| "<network_name> tag...", "Remove tags from given network"), |
| } |
| |
| |
| def Main(): |
| return GenericMain(commands, override={"tag_type": constants.TAG_NETWORK}) |