Introduce client support for networks
authorDimitris Aragiorgis <dimara@grnet.gr>
Mon, 4 Jun 2012 20:09:27 +0000 (23:09 +0300)
committerDimitris Aragiorgis <dimara@grnet.gr>
Wed, 22 Aug 2012 14:11:16 +0000 (17:11 +0300)
gnt-network is used to manipulate and handle networks that
currently provides the following operations:

 * Add a new network:
   gnt-network add --network=1.2.3.0/28 --gateway=1.2.3.1
                   --add-reserved-ips=1.2.3.4,1.2.3.5 testnet

 * Remove an existing network:
   gnt-network remove testnet

 * Modify an existing network:
   gnt-network modify --gateway=1.2.3.6
                      --network-type=private
                      --network6=2001:648::/64
                      --gateway6=none testnet
                      --add-reserved-ips=1.2.3.10,1.2.3.10,
                      --remove-reserved-ips=1.2.3.20
                      testnet

 * Connect an existing network to a nodegroup:
   gnt-network connect testnet default bridged br100
   gnt-network connect testnet <nodegroup> <mode> <link>
   (pass all for <nodegroup> to connect to all nodegroups)

 * Disconnect an existing network from a nodegroup:
   gnt-network disconnect testnet <nodegroup>
   (pass all for <nodegroup> to disconnect from all nodegroups)

 * List available networks:
   gnt-network list

 * Show network info:
   gnt-network info [testnet]

Introduce new option NOCONFLICTSCHECK_OPT for not checking for
conflicting IPs. Using this might cause data inconsistency.

Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

lib/cli.py
lib/client/gnt_instance.py
lib/client/gnt_network.py [new file with mode: 0644]

index 38579b1..bc8936c 100644 (file)
@@ -53,6 +53,7 @@ __all__ = [
   # Command line options
   "ABSOLUTE_OPT",
   "ADD_UIDS_OPT",
+  "ADD_RESERVED_IPS_OPT",
   "ALLOCATABLE_OPT",
   "ALLOC_POLICY_OPT",
   "ALL_OPT",
@@ -86,6 +87,8 @@ __all__ = [
   "FORCE_FILTER_OPT",
   "FORCE_OPT",
   "FORCE_VARIANT_OPT",
+  "GATEWAY_OPT",
+  "GATEWAY6_OPT",
   "GLOBAL_FILEDIR_OPT",
   "HID_OS_OPT",
   "GLOBAL_SHARED_FILEDIR_OPT",
@@ -110,6 +113,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",
@@ -117,6 +123,7 @@ __all__ = [
   "NEW_SECONDARY_OPT",
   "NEW_SPICE_CERT_OPT",
   "NIC_PARAMS_OPT",
+  "NOCONFLICTSCHECK_OPT",
   "NODE_FORCE_JOIN_OPT",
   "NODE_LIST_OPT",
   "NODE_PLACEMENT_OPT",
@@ -159,6 +166,7 @@ __all__ = [
   "READD_OPT",
   "REBOOT_TYPE_OPT",
   "REMOVE_INSTANCE_OPT",
+  "REMOVE_RESERVED_IPS_OPT",
   "REMOVE_UIDS_OPT",
   "RESERVED_LVS_OPT",
   "RUNTIME_MEM_OPT",
@@ -233,11 +241,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",
@@ -245,6 +255,7 @@ __all__ = [
   "ArgHost",
   "ArgInstance",
   "ArgJobId",
+  "ArgNetwork",
   "ArgNode",
   "ArgOs",
   "ArgSuggest",
@@ -255,6 +266,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",
@@ -353,6 +365,11 @@ class ArgNode(_Argument):
   """
 
 
+class ArgNetwork(_Argument):
+  """Network argument.
+
+  """
+
 class ArgGroup(_Argument):
   """Node group argument.
 
@@ -391,9 +408,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)]
@@ -636,8 +655,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,
@@ -645,6 +665,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,
   ])
@@ -1431,6 +1452,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]
 
@@ -1447,6 +1506,7 @@ COMMON_CREATE_OPTS = [
   NET_OPT,
   NODE_PLACEMENT_OPT,
   NOIPCHECK_OPT,
+  NOCONFLICTSCHECK_OPT,
   NONAMECHECK_OPT,
   NONICS_OPT,
   NWSYNC_OPT,
index b814640..5ceda12 100644 (file)
@@ -1419,6 +1419,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
@@ -1605,7 +1606,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 (file)
index 0000000..bd0fe83
--- /dev/null
@@ -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)