Handle the result of QueryGroups() correctly
[ganeti-local] / lib / client / gnt_network.py
1 #
2 #
3
4 # Copyright (C) 2011, 2012 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                     "network_type", "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                             network_type=opts.network_type,
81                             add_reserved_ips=reserved_ips,
82                             conflicts_check=opts.conflicts_check,
83                             tags=tags)
84   SubmitOrSend(op, opts)
85
86
87 def _GetDefaultGroups(cl, groups):
88   """Gets list of groups to operate on.
89
90   If C{groups} doesn't contain groups, a list of all groups in the cluster is
91   returned.
92
93   @type cl: L{luxi.Client}
94   @type groups: list
95   @rtype: list
96
97   """
98   if groups:
99     return groups
100
101   return list(itertools.chain(*cl.QueryGroups([], ["uuid"], False)))
102
103
104 def ConnectNetwork(opts, args):
105   """Map a network to a node group.
106
107   @param opts: the command line options selected by the user
108   @type args: list
109   @param args: Network, mode, physlink and node groups
110   @rtype: int
111   @return: the desired exit code
112
113   """
114   cl = GetClient()
115
116   (network, mode, link) = args[:3]
117   groups = _GetDefaultGroups(cl, 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
141   (network, ) = args[:1]
142   groups = _GetDefaultGroups(cl, args[1:])
143
144   # TODO: Change logic to support "--submit"
145   for group in groups:
146     op = opcodes.OpNetworkDisconnect(group_name=group,
147                                      network_name=network,
148                                      conflicts_check=opts.conflicts_check)
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   return GenericList(constants.QR_NETWORK, desired_fields, args, None,
173                      opts.separator, not opts.no_headers,
174                      verbose=opts.verbose, format_override=fmtoverride)
175
176
177 def ListNetworkFields(opts, args):
178   """List network fields.
179
180   @param opts: the command line options selected by the user
181   @type args: list
182   @param args: fields to list, or empty for all
183   @rtype: int
184   @return: the desired exit code
185
186   """
187   return GenericListFields(constants.QR_NETWORK, args, opts.separator,
188                            not opts.no_headers)
189
190
191 def ShowNetworkConfig(_, args):
192   """Show network information.
193
194   @type args: list
195   @param args: should either be an empty list, in which case
196       we show information about all nodes, or should contain
197       a list of networks (names or UUIDs) to be queried for information
198   @rtype: int
199   @return: the desired exit code
200
201   """
202   cl = GetClient()
203   result = cl.QueryNetworks(fields=["name", "network", "gateway",
204                                     "network6", "gateway6",
205                                     "mac_prefix", "network_type",
206                                     "free_count", "reserved_count",
207                                     "map", "group_list", "inst_list",
208                                     "external_reservations",
209                                     "serial_no", "uuid"],
210                             names=args, use_locking=False)
211
212   for (name, network, gateway, network6, gateway6,
213        mac_prefix, network_type, free_count, reserved_count,
214        mapping, group_list, instances, ext_res, serial, uuid) in result:
215     size = free_count + reserved_count
216     ToStdout("Network name: %s", name)
217     ToStdout("UUID: %s", uuid)
218     ToStdout("Serial number: %d", serial)
219     ToStdout("  Subnet: %s", network)
220     ToStdout("  Gateway: %s", gateway)
221     ToStdout("  IPv6 Subnet: %s", network6)
222     ToStdout("  IPv6 Gateway: %s", gateway6)
223     ToStdout("  Mac Prefix: %s", mac_prefix)
224     ToStdout("  Type: %s", network_type)
225     ToStdout("  Size: %d", size)
226     ToStdout("  Free: %d (%.2f%%)", free_count,
227              100 * float(free_count) / float(size))
228     ToStdout("  Usage map:")
229     idx = 0
230     for line in textwrap.wrap(mapping, width=64):
231       ToStdout("     %s %s %d", str(idx).rjust(3), line.ljust(64), idx + 63)
232       idx += 64
233     ToStdout("         (X) used    (.) free")
234
235     if ext_res:
236       ToStdout("  externally reserved IPs:")
237       for line in textwrap.wrap(ext_res, width=64):
238         ToStdout("    %s" % line)
239
240     if group_list:
241       ToStdout("  connected to node groups:")
242       for group in group_list:
243         ToStdout("    %s", group)
244     else:
245       ToStdout("  not connected to any node group")
246
247     if instances:
248       ToStdout("  used by %d instances:", len(instances))
249       for inst in instances:
250         ((ips, networks), ) = cl.QueryInstances([inst],
251                                                 ["nic.ips", "nic.networks"],
252                                                 use_locking=False)
253
254         l = lambda value: ", ".join(str(idx) + ":" + str(ip)
255                                     for idx, (ip, net) in enumerate(value)
256                                       if net == name)
257
258         ToStdout("    %s : %s", inst, l(zip(ips, networks)))
259     else:
260       ToStdout("  not used by any instances")
261
262
263 def SetNetworkParams(opts, args):
264   """Modifies an IP address pool's parameters.
265
266   @param opts: the command line options selected by the user
267   @type args: list
268   @param args: should contain only one element, the node group name
269
270   @rtype: int
271   @return: the desired exit code
272
273   """
274   # TODO: add "network": opts.network,
275   all_changes = {
276     "gateway": opts.gateway,
277     "add_reserved_ips": _HandleReservedIPs(opts.add_reserved_ips),
278     "remove_reserved_ips": _HandleReservedIPs(opts.remove_reserved_ips),
279     "mac_prefix": opts.mac_prefix,
280     "network_type": opts.network_type,
281     "gateway6": opts.gateway6,
282     "network6": opts.network6,
283   }
284
285   if all_changes.values().count(None) == len(all_changes):
286     ToStderr("Please give at least one of the parameters.")
287     return 1
288
289   # pylint: disable=W0142
290   op = opcodes.OpNetworkSetParams(network_name=args[0], **all_changes)
291
292   # TODO: add feedback to user, e.g. list the modifications
293   SubmitOrSend(op, opts)
294
295
296 def RemoveNetwork(opts, args):
297   """Remove an IP address pool from the cluster.
298
299   @param opts: the command line options selected by the user
300   @type args: list
301   @param args: a list of length 1 with the id of the IP address pool to remove
302   @rtype: int
303   @return: the desired exit code
304
305   """
306   (network_name,) = args
307   op = opcodes.OpNetworkRemove(network_name=network_name, force=opts.force)
308   SubmitOrSend(op, opts)
309
310
311 commands = {
312   "add": (
313     AddNetwork, ARGS_ONE_NETWORK,
314     [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT,
315      MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT,
316      NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT, SUBMIT_OPT],
317     "<network_name>", "Add a new IP network to the cluster"),
318   "list": (
319     ListNetworks, ARGS_MANY_NETWORKS,
320     [NOHDR_OPT, SEP_OPT, FIELDS_OPT, VERBOSE_OPT],
321     "[<network_id>...]",
322     "Lists the IP networks in the cluster. The available fields can be shown"
323     " using the \"list-fields\" command (see the man page for details)."
324     " The default list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS)),
325   "list-fields": (
326     ListNetworkFields, [ArgUnknown()], [NOHDR_OPT, SEP_OPT], "[fields...]",
327     "Lists all available fields for networks"),
328   "info": (
329     ShowNetworkConfig, ARGS_MANY_NETWORKS, [],
330     "[<network_name>...]", "Show information about the network(s)"),
331   "modify": (
332     SetNetworkParams, ARGS_ONE_NETWORK,
333     [DRY_RUN_OPT, SUBMIT_OPT, ADD_RESERVED_IPS_OPT, REMOVE_RESERVED_IPS_OPT,
334      GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT,
335      PRIORITY_OPT],
336     "<network_name>", "Alters the parameters of a network"),
337   "connect": (
338     ConnectNetwork,
339     [ArgNetwork(min=1, max=1),
340      ArgChoice(min=1, max=1, choices=constants.NIC_VALID_MODES),
341      ArgUnknown(min=1, max=1),
342      ArgGroup()],
343     [NOCONFLICTSCHECK_OPT, PRIORITY_OPT],
344     "<network_name> <mode> <link> [<node_group>...]",
345     "Map a given network to the specified node group"
346     " with given mode and link (netparams)"),
347   "disconnect": (
348     DisconnectNetwork,
349     [ArgNetwork(min=1, max=1), ArgGroup()],
350     [NOCONFLICTSCHECK_OPT, PRIORITY_OPT],
351     "<network_name> [<node_group>...]",
352     "Unmap a given network from a specified node group"),
353   "remove": (
354     RemoveNetwork, ARGS_ONE_NETWORK,
355     [FORCE_OPT, DRY_RUN_OPT, SUBMIT_OPT, PRIORITY_OPT],
356     "[--dry-run] <network_id>",
357     "Remove an (empty) network from the cluster"),
358   "list-tags": (
359     ListTags, ARGS_ONE_NETWORK, [],
360     "<network_name>", "List the tags of the given network"),
361   "add-tags": (
362     AddTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
363     [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
364     "<network_name> tag...", "Add tags to the given network"),
365   "remove-tags": (
366     RemoveTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
367     [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
368     "<network_name> tag...", "Remove tags from given network"),
369 }
370
371
372 def Main():
373   return GenericMain(commands, override={"tag_type": constants.TAG_NETWORK})