gnt_network.py 12.6 KB
Newer Older
1 2 3
#
#

4
# Copyright (C) 2011, 2012, 2013 Google Inc.
Klaus Aehlig's avatar
Klaus Aehlig committed
5
# All rights reserved.
6
#
Klaus Aehlig's avatar
Klaus Aehlig committed
7 8 9
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
10
#
Klaus Aehlig's avatar
Klaus Aehlig committed
11 12
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
13
#
Klaus Aehlig's avatar
Klaus Aehlig committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 30 31

"""IP pool related commands"""

32
# pylint: disable=W0401,W0614
33 34 35
# W0401: Wildcard import ganeti.cli
# W0614: Unused import %s from wildcard import (since we need cli)

36
import textwrap
37
import itertools
38

39 40 41 42
from ganeti.cli import *
from ganeti import constants
from ganeti import opcodes
from ganeti import utils
43
from ganeti import errors
44
from ganeti import objects
45 46 47


#: default list of fields for L{ListNetworks}
48
_LIST_DEF_FIELDS = ["name", "network", "gateway",
49
                    "mac_prefix", "group_list", "tags"]
50 51 52


def _HandleReservedIPs(ips):
53 54 55 56 57 58
  if ips is None:
    return None
  elif not ips:
    return []
  else:
    return utils.UnescapeAndSplit(ips, sep=",")
59

60

61 62 63 64 65 66 67 68 69 70 71 72
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

73 74 75 76
  if opts.network is None:
    raise errors.OpPrereqError("The --network option must be given",
                               errors.ECODE_INVAL)

77 78 79 80 81
  if opts.tags is not None:
    tags = opts.tags.split(",")
  else:
    tags = []

82 83 84 85 86 87 88 89 90 91 92
  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)
93
  SubmitOrSend(op, opts)
94 95


96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
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)))


113
def ConnectNetwork(opts, args):
114 115 116 117
  """Map a network to a node group.

  @param opts: the command line options selected by the user
  @type args: list
118
  @param args: Network, mode, physlink and node groups
119 120 121 122
  @rtype: int
  @return: the desired exit code

  """
123
  cl = GetClient()
124

125 126 127 128
  network = args[0]
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, opts.nicparams)

  groups = _GetDefaultGroups(cl, args[1:])
129

130
  # TODO: Change logic to support "--submit"
131 132 133
  for group in groups:
    op = opcodes.OpNetworkConnect(group_name=group,
                                  network_name=network,
134 135 136
                                  network_mode=nicparams[constants.NIC_MODE],
                                  network_link=nicparams[constants.NIC_LINK],
                                  network_vlan=nicparams[constants.NIC_VLAN],
137
                                  conflicts_check=opts.conflicts_check)
138
    SubmitOpCode(op, opts=opts, cl=cl)
139 140


141
def DisconnectNetwork(opts, args):
142 143 144 145
  """Unmap a network from a node group.

  @param opts: the command line options selected by the user
  @type args: list
146
  @param args: Network and node groups
147 148 149 150
  @rtype: int
  @return: the desired exit code

  """
151
  cl = GetClient()
152
  qcl = GetClient(query=True)
153

154
  (network, ) = args[:1]
155
  groups = _GetDefaultGroups(qcl, args[1:])
156

157
  # TODO: Change logic to support "--submit"
158 159
  for group in groups:
    op = opcodes.OpNetworkDisconnect(group_name=group,
160
                                     network_name=network)
161
    SubmitOpCode(op, opts=opts, cl=cl)
162 163 164 165 166 167 168 169 170 171 172 173 174 175


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 = {
176
    "group_list":
177 178 179
      (lambda data:
        utils.CommaJoin("%s (%s, %s, %s)" % (name, mode, link, vlan)
                        for (name, mode, link, vlan) in data),
180
       False),
181
    "inst_list": (",".join, False),
182
    "tags": (",".join, False),
183
    }
184

185
  cl = GetClient(query=True)
186 187
  return GenericList(constants.QR_NETWORK, desired_fields, args, None,
                     opts.separator, not opts.no_headers,
188 189
                     verbose=opts.verbose, format_override=fmtoverride,
                     cl=cl)
190 191 192 193 194 195 196 197 198 199 200 201


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

  """
202 203
  cl = GetClient(query=True)

204
  return GenericListFields(constants.QR_NETWORK, args, opts.separator,
205
                           not opts.no_headers, cl=cl)
206 207


208
def ShowNetworkConfig(_, args):
209 210 211 212 213 214 215 216 217 218
  """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

  """
219
  cl = GetClient(query=True)
220 221
  result = cl.QueryNetworks(fields=["name", "network", "gateway",
                                    "network6", "gateway6",
222
                                    "mac_prefix",
223 224
                                    "free_count", "reserved_count",
                                    "map", "group_list", "inst_list",
225 226
                                    "external_reservations",
                                    "serial_no", "uuid"],
227 228 229
                            names=args, use_locking=False)

  for (name, network, gateway, network6, gateway6,
230
       mac_prefix, free_count, reserved_count,
231
       mapping, group_list, instances, ext_res, serial, uuid) in result:
232 233
    size = free_count + reserved_count
    ToStdout("Network name: %s", name)
234 235 236 237 238 239 240 241 242
    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,
243
             100 * float(free_count) / float(size))
244
    ToStdout("  Usage map:")
245
    lenmapping = len(mapping)
246
    idx = 0
247 248
    while idx < lenmapping:
      line = mapping[idx: idx + 64]
249
      ToStdout("     %s %s %d", str(idx).rjust(4), line.ljust(64), idx + 63)
250 251 252 253 254
      idx += 64
    ToStdout("         (X) used    (.) free")

    if ext_res:
      ToStdout("  externally reserved IPs:")
255
      for line in textwrap.wrap(ext_res, width=64):
256 257 258 259
        ToStdout("    %s" % line)

    if group_list:
      ToStdout("  connected to node groups:")
260 261 262
      for group, nic_mode, nic_link, nic_vlan in group_list:
        ToStdout("    %s (mode:%s link:%s vlan:%s)",
                 group, nic_mode, nic_link, nic_vlan)
263 264 265 266 267
    else:
      ToStdout("  not connected to any node group")

    if instances:
      ToStdout("  used by %d instances:", len(instances))
268
      for name in instances:
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
269
        ((ips, networks), ) = cl.QueryInstances([name],
270 271 272
                                                ["nic.ips", "nic.networks"],
                                                use_locking=False)

273
        l = lambda value: ", ".join(str(idx) + ":" + str(ip)
274
                                    for idx, (ip, net) in enumerate(value)
275
                                      if net == uuid)
276

Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
277
        ToStdout("    %s: %s", name, l(zip(ips, networks)))
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
    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

307 308
  # pylint: disable=W0142
  op = opcodes.OpNetworkSetParams(network_name=args[0], **all_changes)
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325

  # 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)
326
  SubmitOrSend(op, opts)
327 328 329 330 331


commands = {
  "add": (
    AddNetwork, ARGS_ONE_NETWORK,
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
332
    [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT,
333
     MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT,
334
     NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
    "<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,
351 352 353 354
    [DRY_RUN_OPT] + SUBMIT_OPTS +
    [ADD_RESERVED_IPS_OPT,
     REMOVE_RESERVED_IPS_OPT, GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK6_OPT,
     GATEWAY6_OPT, PRIORITY_OPT],
355 356
    "<network_name>", "Alters the parameters of a network"),
  "connect": (
357 358 359
    ConnectNetwork,
    [ArgNetwork(min=1, max=1),
     ArgGroup()],
360
    [NOCONFLICTSCHECK_OPT, PRIORITY_OPT, NIC_PARAMS_OPT],
361
    "<network_name> [<node_group>...]",
362 363 364
    "Map a given network to the specified node group"
    " with given mode and link (netparams)"),
  "disconnect": (
365 366
    DisconnectNetwork,
    [ArgNetwork(min=1, max=1), ArgGroup()],
367
    [PRIORITY_OPT],
368
    "<network_name> [<node_group>...]",
369 370
    "Unmap a given network from a specified node group"),
  "remove": (
371
    RemoveNetwork, ARGS_ONE_NETWORK,
372
    [FORCE_OPT, DRY_RUN_OPT] + SUBMIT_OPTS + [PRIORITY_OPT],
373 374
    "[--dry-run] <network_id>",
    "Remove an (empty) network from the cluster"),
375 376 377 378 379
  "list-tags": (
    ListTags, ARGS_ONE_NETWORK, [],
    "<network_name>", "List the tags of the given network"),
  "add-tags": (
    AddTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
380
    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
381 382 383
    "<network_name> tag...", "Add tags to the given network"),
  "remove-tags": (
    RemoveTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
384
    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
385
    "<network_name> tag...", "Remove tags from given network"),
386 387 388 389
}


def Main():
390
  return GenericMain(commands, override={"tag_type": constants.TAG_NETWORK})