Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_network.py @ 653bc0f1

History | View | Annotate | Download (12 kB)

1
#
2
#
3

    
4
# Copyright (C) 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""IP pool related commands"""
22

    
23
# pylint: disable=W0401,W0614
24
# W0401: Wildcard import ganeti.cli
25
# W0614: Unused import %s from wildcard import (since we need cli)
26

    
27
import textwrap
28
import itertools
29

    
30
from ganeti.cli import *
31
from ganeti import constants
32
from ganeti import opcodes
33
from ganeti import utils
34
from ganeti import errors
35

    
36

    
37
#: default list of fields for L{ListNetworks}
38
_LIST_DEF_FIELDS = ["name", "network", "gateway",
39
                    "mac_prefix", "group_list", "tags"]
40

    
41

    
42
def _HandleReservedIPs(ips):
43
  if ips is None:
44
    return None
45
  elif not ips:
46
    return []
47
  else:
48
    return utils.UnescapeAndSplit(ips, sep=",")
49

    
50

    
51
def AddNetwork(opts, args):
52
  """Add a network to the cluster.
53

54
  @param opts: the command line options selected by the user
55
  @type args: list
56
  @param args: a list of length 1 with the network name to create
57
  @rtype: int
58
  @return: the desired exit code
59

60
  """
61
  (network_name, ) = args
62

    
63
  if opts.network is None:
64
    raise errors.OpPrereqError("The --network option must be given",
65
                               errors.ECODE_INVAL)
66

    
67
  if opts.tags is not None:
68
    tags = opts.tags.split(",")
69
  else:
70
    tags = []
71

    
72
  reserved_ips = _HandleReservedIPs(opts.add_reserved_ips)
73

    
74
  op = opcodes.OpNetworkAdd(network_name=network_name,
75
                            gateway=opts.gateway,
76
                            network=opts.network,
77
                            gateway6=opts.gateway6,
78
                            network6=opts.network6,
79
                            mac_prefix=opts.mac_prefix,
80
                            add_reserved_ips=reserved_ips,
81
                            conflicts_check=opts.conflicts_check,
82
                            tags=tags)
83
  SubmitOrSend(op, opts)
84

    
85

    
86
def _GetDefaultGroups(cl, groups):
87
  """Gets list of groups to operate on.
88

89
  If C{groups} doesn't contain groups, a list of all groups in the cluster is
90
  returned.
91

92
  @type cl: L{luxi.Client}
93
  @type groups: list
94
  @rtype: list
95

96
  """
97
  if groups:
98
    return groups
99

    
100
  return list(itertools.chain(*cl.QueryGroups([], ["uuid"], False)))
101

    
102

    
103
def ConnectNetwork(opts, args):
104
  """Map a network to a node group.
105

106
  @param opts: the command line options selected by the user
107
  @type args: list
108
  @param args: Network, mode, physlink and node groups
109
  @rtype: int
110
  @return: the desired exit code
111

112
  """
113
  cl = GetClient()
114
  qcl = GetClient(query=True)
115

    
116
  (network, mode, link) = args[:3]
117
  groups = _GetDefaultGroups(qcl, args[3:])
118

    
119
  # TODO: Change logic to support "--submit"
120
  for group in groups:
121
    op = opcodes.OpNetworkConnect(group_name=group,
122
                                  network_name=network,
123
                                  network_mode=mode,
124
                                  network_link=link,
125
                                  conflicts_check=opts.conflicts_check)
126
    SubmitOpCode(op, opts=opts, cl=cl)
127

    
128

    
129
def DisconnectNetwork(opts, args):
130
  """Unmap a network from a node group.
131

132
  @param opts: the command line options selected by the user
133
  @type args: list
134
  @param args: Network and node groups
135
  @rtype: int
136
  @return: the desired exit code
137

138
  """
139
  cl = GetClient()
140
  qcl = GetClient(query=True)
141

    
142
  (network, ) = args[:1]
143
  groups = _GetDefaultGroups(qcl, args[1:])
144

    
145
  # TODO: Change logic to support "--submit"
146
  for group in groups:
147
    op = opcodes.OpNetworkDisconnect(group_name=group,
148
                                     network_name=network)
149
    SubmitOpCode(op, opts=opts, cl=cl)
150

    
151

    
152
def ListNetworks(opts, args):
153
  """List Ip pools and their properties.
154

155
  @param opts: the command line options selected by the user
156
  @type args: list
157
  @param args: networks to list, or empty for all
158
  @rtype: int
159
  @return: the desired exit code
160

161
  """
162
  desired_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
163
  fmtoverride = {
164
    "group_list":
165
      (lambda data: utils.CommaJoin("%s (%s, %s)" % (name, mode, link)
166
                                    for (name, mode, link) in data),
167
       False),
168
    "inst_list": (",".join, False),
169
    "tags": (",".join, False),
170
    }
171

    
172
  cl = GetClient(query=True)
173
  return GenericList(constants.QR_NETWORK, desired_fields, args, None,
174
                     opts.separator, not opts.no_headers,
175
                     verbose=opts.verbose, format_override=fmtoverride,
176
                     cl=cl)
177

    
178

    
179
def ListNetworkFields(opts, args):
180
  """List network fields.
181

182
  @param opts: the command line options selected by the user
183
  @type args: list
184
  @param args: fields to list, or empty for all
185
  @rtype: int
186
  @return: the desired exit code
187

188
  """
189
  cl = GetClient(query=True)
190

    
191
  return GenericListFields(constants.QR_NETWORK, args, opts.separator,
192
                           not opts.no_headers, cl=cl)
193

    
194

    
195
def ShowNetworkConfig(_, args):
196
  """Show network information.
197

198
  @type args: list
199
  @param args: should either be an empty list, in which case
200
      we show information about all nodes, or should contain
201
      a list of networks (names or UUIDs) to be queried for information
202
  @rtype: int
203
  @return: the desired exit code
204

205
  """
206
  cl = GetClient(query=True)
207
  result = cl.QueryNetworks(fields=["name", "network", "gateway",
208
                                    "network6", "gateway6",
209
                                    "mac_prefix",
210
                                    "free_count", "reserved_count",
211
                                    "map", "group_list", "inst_list",
212
                                    "external_reservations",
213
                                    "serial_no", "uuid"],
214
                            names=args, use_locking=False)
215

    
216
  for (name, network, gateway, network6, gateway6,
217
       mac_prefix, free_count, reserved_count,
218
       mapping, group_list, instances, ext_res, serial, uuid) in result:
219
    size = free_count + reserved_count
220
    ToStdout("Network name: %s", name)
221
    ToStdout("UUID: %s", uuid)
222
    ToStdout("Serial number: %d", serial)
223
    ToStdout("  Subnet: %s", network)
224
    ToStdout("  Gateway: %s", gateway)
225
    ToStdout("  IPv6 Subnet: %s", network6)
226
    ToStdout("  IPv6 Gateway: %s", gateway6)
227
    ToStdout("  Mac Prefix: %s", mac_prefix)
228
    ToStdout("  Size: %d", size)
229
    ToStdout("  Free: %d (%.2f%%)", free_count,
230
             100 * float(free_count) / float(size))
231
    ToStdout("  Usage map:")
232
    idx = 0
233
    for line in textwrap.wrap(mapping, width=64):
234
      ToStdout("     %s %s %d", str(idx).rjust(3), line.ljust(64), idx + 63)
235
      idx += 64
236
    ToStdout("         (X) used    (.) free")
237

    
238
    if ext_res:
239
      ToStdout("  externally reserved IPs:")
240
      for line in textwrap.wrap(ext_res, width=64):
241
        ToStdout("    %s" % line)
242

    
243
    if group_list:
244
      ToStdout("  connected to node groups:")
245
      for group, nic_mode, nic_link in group_list:
246
        ToStdout("    %s (%s on %s)", group, nic_mode, nic_link)
247
    else:
248
      ToStdout("  not connected to any node group")
249

    
250
    if instances:
251
      idata = cl.QueryInstances([], ["uuid", "name"], False)
252
      uuid2name = dict(idata)
253

    
254
      ToStdout("  used by %d instances:", len(instances))
255
      for inst in instances:
256
        name = uuid2name[inst]
257
        ((ips, networks), ) = cl.QueryInstances([name],
258
                                                ["nic.ips", "nic.networks"],
259
                                                use_locking=False)
260

    
261
        l = lambda value: ", ".join(str(idx) + ":" + str(ip)
262
                                    for idx, (ip, net) in enumerate(value)
263
                                      if net == uuid)
264

    
265
        ToStdout("    %s: %s", name, l(zip(ips, networks)))
266
    else:
267
      ToStdout("  not used by any instances")
268

    
269

    
270
def SetNetworkParams(opts, args):
271
  """Modifies an IP address pool's parameters.
272

273
  @param opts: the command line options selected by the user
274
  @type args: list
275
  @param args: should contain only one element, the node group name
276

277
  @rtype: int
278
  @return: the desired exit code
279

280
  """
281
  # TODO: add "network": opts.network,
282
  all_changes = {
283
    "gateway": opts.gateway,
284
    "add_reserved_ips": _HandleReservedIPs(opts.add_reserved_ips),
285
    "remove_reserved_ips": _HandleReservedIPs(opts.remove_reserved_ips),
286
    "mac_prefix": opts.mac_prefix,
287
    "gateway6": opts.gateway6,
288
    "network6": opts.network6,
289
  }
290

    
291
  if all_changes.values().count(None) == len(all_changes):
292
    ToStderr("Please give at least one of the parameters.")
293
    return 1
294

    
295
  # pylint: disable=W0142
296
  op = opcodes.OpNetworkSetParams(network_name=args[0], **all_changes)
297

    
298
  # TODO: add feedback to user, e.g. list the modifications
299
  SubmitOrSend(op, opts)
300

    
301

    
302
def RemoveNetwork(opts, args):
303
  """Remove an IP address pool from the cluster.
304

305
  @param opts: the command line options selected by the user
306
  @type args: list
307
  @param args: a list of length 1 with the id of the IP address pool to remove
308
  @rtype: int
309
  @return: the desired exit code
310

311
  """
312
  (network_name,) = args
313
  op = opcodes.OpNetworkRemove(network_name=network_name, force=opts.force)
314
  SubmitOrSend(op, opts)
315

    
316

    
317
commands = {
318
  "add": (
319
    AddNetwork, ARGS_ONE_NETWORK,
320
    [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT,
321
     MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT,
322
     NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
323
    "<network_name>", "Add a new IP network to the cluster"),
324
  "list": (
325
    ListNetworks, ARGS_MANY_NETWORKS,
326
    [NOHDR_OPT, SEP_OPT, FIELDS_OPT, VERBOSE_OPT],
327
    "[<network_id>...]",
328
    "Lists the IP networks in the cluster. The available fields can be shown"
329
    " using the \"list-fields\" command (see the man page for details)."
330
    " The default list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS)),
331
  "list-fields": (
332
    ListNetworkFields, [ArgUnknown()], [NOHDR_OPT, SEP_OPT], "[fields...]",
333
    "Lists all available fields for networks"),
334
  "info": (
335
    ShowNetworkConfig, ARGS_MANY_NETWORKS, [],
336
    "[<network_name>...]", "Show information about the network(s)"),
337
  "modify": (
338
    SetNetworkParams, ARGS_ONE_NETWORK,
339
    [DRY_RUN_OPT] + SUBMIT_OPTS +
340
    [ADD_RESERVED_IPS_OPT,
341
     REMOVE_RESERVED_IPS_OPT, GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK6_OPT,
342
     GATEWAY6_OPT, PRIORITY_OPT],
343
    "<network_name>", "Alters the parameters of a network"),
344
  "connect": (
345
    ConnectNetwork,
346
    [ArgNetwork(min=1, max=1),
347
     ArgChoice(min=1, max=1, choices=constants.NIC_VALID_MODES),
348
     ArgUnknown(min=1, max=1),
349
     ArgGroup()],
350
    [NOCONFLICTSCHECK_OPT, PRIORITY_OPT],
351
    "<network_name> <mode> <link> [<node_group>...]",
352
    "Map a given network to the specified node group"
353
    " with given mode and link (netparams)"),
354
  "disconnect": (
355
    DisconnectNetwork,
356
    [ArgNetwork(min=1, max=1), ArgGroup()],
357
    [PRIORITY_OPT],
358
    "<network_name> [<node_group>...]",
359
    "Unmap a given network from a specified node group"),
360
  "remove": (
361
    RemoveNetwork, ARGS_ONE_NETWORK,
362
    [FORCE_OPT, DRY_RUN_OPT] + SUBMIT_OPTS + [PRIORITY_OPT],
363
    "[--dry-run] <network_id>",
364
    "Remove an (empty) network from the cluster"),
365
  "list-tags": (
366
    ListTags, ARGS_ONE_NETWORK, [],
367
    "<network_name>", "List the tags of the given network"),
368
  "add-tags": (
369
    AddTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
370
    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
371
    "<network_name> tag...", "Add tags to the given network"),
372
  "remove-tags": (
373
    RemoveTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
374
    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
375
    "<network_name> tag...", "Remove tags from given network"),
376
}
377

    
378

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