32e3ff7d4e560c0ea24601488c241cb0d4c687b1
[ganeti-local] / lib / cmdlib / network.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 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
22 """Logical units dealing with networks."""
23
24 from ganeti import constants
25 from ganeti import errors
26 from ganeti import locking
27 from ganeti import network
28 from ganeti import objects
29 from ganeti import qlang
30 from ganeti import query
31 from ganeti import utils
32 from ganeti.cmdlib.base import LogicalUnit, NoHooksLU, QueryBase
33 from ganeti.cmdlib.common import ShareAll
34
35
36 def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6,
37                          mac_prefix, tags):
38   """Builds network related env variables for hooks
39
40   This builds the hook environment from individual variables.
41
42   @type name: string
43   @param name: the name of the network
44   @type subnet: string
45   @param subnet: the ipv4 subnet
46   @type gateway: string
47   @param gateway: the ipv4 gateway
48   @type network6: string
49   @param network6: the ipv6 subnet
50   @type gateway6: string
51   @param gateway6: the ipv6 gateway
52   @type mac_prefix: string
53   @param mac_prefix: the mac_prefix
54   @type tags: list
55   @param tags: the tags of the network
56
57   """
58   env = {}
59   if name:
60     env["NETWORK_NAME"] = name
61   if subnet:
62     env["NETWORK_SUBNET"] = subnet
63   if gateway:
64     env["NETWORK_GATEWAY"] = gateway
65   if network6:
66     env["NETWORK_SUBNET6"] = network6
67   if gateway6:
68     env["NETWORK_GATEWAY6"] = gateway6
69   if mac_prefix:
70     env["NETWORK_MAC_PREFIX"] = mac_prefix
71   if tags:
72     env["NETWORK_TAGS"] = " ".join(tags)
73
74   return env
75
76
77 class LUNetworkAdd(LogicalUnit):
78   """Logical unit for creating networks.
79
80   """
81   HPATH = "network-add"
82   HTYPE = constants.HTYPE_NETWORK
83   REQ_BGL = False
84
85   def BuildHooksNodes(self):
86     """Build hooks nodes.
87
88     """
89     mn = self.cfg.GetMasterNode()
90     return ([mn], [mn])
91
92   def CheckArguments(self):
93     if self.op.mac_prefix:
94       self.op.mac_prefix = \
95         utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix)
96
97   def ExpandNames(self):
98     self.network_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
99
100     if self.op.conflicts_check:
101       self.share_locks[locking.LEVEL_NODE] = 1
102       self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
103       self.needed_locks = {
104         locking.LEVEL_NODE: locking.ALL_SET,
105         locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
106         }
107     else:
108       self.needed_locks = {}
109
110     self.add_locks[locking.LEVEL_NETWORK] = self.network_uuid
111
112   def CheckPrereq(self):
113     if self.op.network is None:
114       raise errors.OpPrereqError("Network must be given",
115                                  errors.ECODE_INVAL)
116
117     try:
118       existing_uuid = self.cfg.LookupNetwork(self.op.network_name)
119     except errors.OpPrereqError:
120       pass
121     else:
122       raise errors.OpPrereqError("Desired network name '%s' already exists as a"
123                                  " network (UUID: %s)" %
124                                  (self.op.network_name, existing_uuid),
125                                  errors.ECODE_EXISTS)
126
127     # Check tag validity
128     for tag in self.op.tags:
129       objects.TaggableObject.ValidateTag(tag)
130
131   def BuildHooksEnv(self):
132     """Build hooks env.
133
134     """
135     args = {
136       "name": self.op.network_name,
137       "subnet": self.op.network,
138       "gateway": self.op.gateway,
139       "network6": self.op.network6,
140       "gateway6": self.op.gateway6,
141       "mac_prefix": self.op.mac_prefix,
142       "tags": self.op.tags,
143       }
144     return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
145
146   def Exec(self, feedback_fn):
147     """Add the ip pool to the cluster.
148
149     """
150     nobj = objects.Network(name=self.op.network_name,
151                            network=self.op.network,
152                            gateway=self.op.gateway,
153                            network6=self.op.network6,
154                            gateway6=self.op.gateway6,
155                            mac_prefix=self.op.mac_prefix,
156                            uuid=self.network_uuid)
157     # Initialize the associated address pool
158     try:
159       pool = network.AddressPool.InitializeNetwork(nobj)
160     except errors.AddressPoolError, err:
161       raise errors.OpExecError("Cannot create IP address pool for network"
162                                " '%s': %s" % (self.op.network_name, err))
163
164     # Check if we need to reserve the nodes and the cluster master IP
165     # These may not be allocated to any instances in routed mode, as
166     # they wouldn't function anyway.
167     if self.op.conflicts_check:
168       for node in self.cfg.GetAllNodesInfo().values():
169         for ip in [node.primary_ip, node.secondary_ip]:
170           try:
171             if pool.Contains(ip):
172               pool.Reserve(ip, external=True)
173               self.LogInfo("Reserved IP address of node '%s' (%s)",
174                            node.name, ip)
175           except errors.AddressPoolError, err:
176             self.LogWarning("Cannot reserve IP address '%s' of node '%s': %s",
177                             ip, node.name, err)
178
179       master_ip = self.cfg.GetClusterInfo().master_ip
180       try:
181         if pool.Contains(master_ip):
182           pool.Reserve(master_ip, external=True)
183           self.LogInfo("Reserved cluster master IP address (%s)", master_ip)
184       except errors.AddressPoolError, err:
185         self.LogWarning("Cannot reserve cluster master IP address (%s): %s",
186                         master_ip, err)
187
188     if self.op.add_reserved_ips:
189       for ip in self.op.add_reserved_ips:
190         try:
191           pool.Reserve(ip, external=True)
192         except errors.AddressPoolError, err:
193           raise errors.OpExecError("Cannot reserve IP address '%s': %s" %
194                                    (ip, err))
195
196     if self.op.tags:
197       for tag in self.op.tags:
198         nobj.AddTag(tag)
199
200     self.cfg.AddNetwork(nobj, self.proc.GetECId(), check_uuid=False)
201     del self.remove_locks[locking.LEVEL_NETWORK]
202
203
204 class LUNetworkRemove(LogicalUnit):
205   HPATH = "network-remove"
206   HTYPE = constants.HTYPE_NETWORK
207   REQ_BGL = False
208
209   def ExpandNames(self):
210     self.network_uuid = self.cfg.LookupNetwork(self.op.network_name)
211
212     self.share_locks[locking.LEVEL_NODEGROUP] = 1
213     self.needed_locks = {
214       locking.LEVEL_NETWORK: [self.network_uuid],
215       locking.LEVEL_NODEGROUP: locking.ALL_SET,
216       }
217
218   def CheckPrereq(self):
219     """Check prerequisites.
220
221     This checks that the given network name exists as a network, that is
222     empty (i.e., contains no nodes), and that is not the last group of the
223     cluster.
224
225     """
226     # Verify that the network is not conncted.
227     node_groups = [group.name
228                    for group in self.cfg.GetAllNodeGroupsInfo().values()
229                    if self.network_uuid in group.networks]
230
231     if node_groups:
232       self.LogWarning("Network '%s' is connected to the following"
233                       " node groups: %s" %
234                       (self.op.network_name,
235                        utils.CommaJoin(utils.NiceSort(node_groups))))
236       raise errors.OpPrereqError("Network still connected", errors.ECODE_STATE)
237
238   def BuildHooksEnv(self):
239     """Build hooks env.
240
241     """
242     return {
243       "NETWORK_NAME": self.op.network_name,
244       }
245
246   def BuildHooksNodes(self):
247     """Build hooks nodes.
248
249     """
250     mn = self.cfg.GetMasterNode()
251     return ([mn], [mn])
252
253   def Exec(self, feedback_fn):
254     """Remove the network.
255
256     """
257     try:
258       self.cfg.RemoveNetwork(self.network_uuid)
259     except errors.ConfigurationError:
260       raise errors.OpExecError("Network '%s' with UUID %s disappeared" %
261                                (self.op.network_name, self.network_uuid))
262
263
264 class LUNetworkSetParams(LogicalUnit):
265   """Modifies the parameters of a network.
266
267   """
268   HPATH = "network-modify"
269   HTYPE = constants.HTYPE_NETWORK
270   REQ_BGL = False
271
272   def CheckArguments(self):
273     if (self.op.gateway and
274         (self.op.add_reserved_ips or self.op.remove_reserved_ips)):
275       raise errors.OpPrereqError("Cannot modify gateway and reserved ips"
276                                  " at once", errors.ECODE_INVAL)
277
278   def ExpandNames(self):
279     self.network_uuid = self.cfg.LookupNetwork(self.op.network_name)
280
281     self.needed_locks = {
282       locking.LEVEL_NETWORK: [self.network_uuid],
283       }
284
285   def CheckPrereq(self):
286     """Check prerequisites.
287
288     """
289     self.network = self.cfg.GetNetwork(self.network_uuid)
290     self.gateway = self.network.gateway
291     self.mac_prefix = self.network.mac_prefix
292     self.network6 = self.network.network6
293     self.gateway6 = self.network.gateway6
294     self.tags = self.network.tags
295
296     self.pool = network.AddressPool(self.network)
297
298     if self.op.gateway:
299       if self.op.gateway == constants.VALUE_NONE:
300         self.gateway = None
301       else:
302         self.gateway = self.op.gateway
303         if self.pool.IsReserved(self.gateway):
304           raise errors.OpPrereqError("Gateway IP address '%s' is already"
305                                      " reserved" % self.gateway,
306                                      errors.ECODE_STATE)
307
308     if self.op.mac_prefix:
309       if self.op.mac_prefix == constants.VALUE_NONE:
310         self.mac_prefix = None
311       else:
312         self.mac_prefix = \
313           utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix)
314
315     if self.op.gateway6:
316       if self.op.gateway6 == constants.VALUE_NONE:
317         self.gateway6 = None
318       else:
319         self.gateway6 = self.op.gateway6
320
321     if self.op.network6:
322       if self.op.network6 == constants.VALUE_NONE:
323         self.network6 = None
324       else:
325         self.network6 = self.op.network6
326
327   def BuildHooksEnv(self):
328     """Build hooks env.
329
330     """
331     args = {
332       "name": self.op.network_name,
333       "subnet": self.network.network,
334       "gateway": self.gateway,
335       "network6": self.network6,
336       "gateway6": self.gateway6,
337       "mac_prefix": self.mac_prefix,
338       "tags": self.tags,
339       }
340     return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
341
342   def BuildHooksNodes(self):
343     """Build hooks nodes.
344
345     """
346     mn = self.cfg.GetMasterNode()
347     return ([mn], [mn])
348
349   def Exec(self, feedback_fn):
350     """Modifies the network.
351
352     """
353     #TODO: reserve/release via temporary reservation manager
354     #      extend cfg.ReserveIp/ReleaseIp with the external flag
355     if self.op.gateway:
356       if self.gateway == self.network.gateway:
357         self.LogWarning("Gateway is already %s", self.gateway)
358       else:
359         if self.gateway:
360           self.pool.Reserve(self.gateway, external=True)
361         if self.network.gateway:
362           self.pool.Release(self.network.gateway, external=True)
363         self.network.gateway = self.gateway
364
365     if self.op.add_reserved_ips:
366       for ip in self.op.add_reserved_ips:
367         try:
368           if self.pool.IsReserved(ip):
369             self.LogWarning("IP address %s is already reserved", ip)
370           else:
371             self.pool.Reserve(ip, external=True)
372         except errors.AddressPoolError, err:
373           self.LogWarning("Cannot reserve IP address %s: %s", ip, err)
374
375     if self.op.remove_reserved_ips:
376       for ip in self.op.remove_reserved_ips:
377         if ip == self.network.gateway:
378           self.LogWarning("Cannot unreserve Gateway's IP")
379           continue
380         try:
381           if not self.pool.IsReserved(ip):
382             self.LogWarning("IP address %s is already unreserved", ip)
383           else:
384             self.pool.Release(ip, external=True)
385         except errors.AddressPoolError, err:
386           self.LogWarning("Cannot release IP address %s: %s", ip, err)
387
388     if self.op.mac_prefix:
389       self.network.mac_prefix = self.mac_prefix
390
391     if self.op.network6:
392       self.network.network6 = self.network6
393
394     if self.op.gateway6:
395       self.network.gateway6 = self.gateway6
396
397     self.pool.Validate()
398
399     self.cfg.Update(self.network, feedback_fn)
400
401
402 class NetworkQuery(QueryBase):
403   FIELDS = query.NETWORK_FIELDS
404
405   def ExpandNames(self, lu):
406     lu.needed_locks = {}
407     lu.share_locks = ShareAll()
408
409     self.do_locking = self.use_locking
410
411     all_networks = lu.cfg.GetAllNetworksInfo()
412     name_to_uuid = dict((n.name, n.uuid) for n in all_networks.values())
413
414     if self.names:
415       missing = []
416       self.wanted = []
417
418       for name in self.names:
419         if name in name_to_uuid:
420           self.wanted.append(name_to_uuid[name])
421         else:
422           missing.append(name)
423
424       if missing:
425         raise errors.OpPrereqError("Some networks do not exist: %s" % missing,
426                                    errors.ECODE_NOENT)
427     else:
428       self.wanted = locking.ALL_SET
429
430     if self.do_locking:
431       lu.needed_locks[locking.LEVEL_NETWORK] = self.wanted
432       if query.NETQ_INST in self.requested_data:
433         lu.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
434       if query.NETQ_GROUP in self.requested_data:
435         lu.needed_locks[locking.LEVEL_NODEGROUP] = locking.ALL_SET
436
437   def DeclareLocks(self, lu, level):
438     pass
439
440   def _GetQueryData(self, lu):
441     """Computes the list of networks and their attributes.
442
443     """
444     all_networks = lu.cfg.GetAllNetworksInfo()
445
446     network_uuids = self._GetNames(lu, all_networks.keys(),
447                                    locking.LEVEL_NETWORK)
448
449     do_instances = query.NETQ_INST in self.requested_data
450     do_groups = query.NETQ_GROUP in self.requested_data
451
452     network_to_instances = None
453     network_to_groups = None
454
455     # For NETQ_GROUP, we need to map network->[groups]
456     if do_groups:
457       all_groups = lu.cfg.GetAllNodeGroupsInfo()
458       network_to_groups = dict((uuid, []) for uuid in network_uuids)
459       for _, group in all_groups.iteritems():
460         for net_uuid in network_uuids:
461           netparams = group.networks.get(net_uuid, None)
462           if netparams:
463             info = (group.name, netparams[constants.NIC_MODE],
464                     netparams[constants.NIC_LINK])
465
466             network_to_groups[net_uuid].append(info)
467
468     if do_instances:
469       all_instances = lu.cfg.GetAllInstancesInfo()
470       network_to_instances = dict((uuid, []) for uuid in network_uuids)
471       for instance in all_instances.values():
472         for nic in instance.nics:
473           if nic.network in network_uuids:
474             network_to_instances[nic.network].append(instance.name)
475             break
476
477     if query.NETQ_STATS in self.requested_data:
478       stats = \
479         dict((uuid,
480               self._GetStats(network.AddressPool(all_networks[uuid])))
481              for uuid in network_uuids)
482     else:
483       stats = None
484
485     return query.NetworkQueryData([all_networks[uuid]
486                                    for uuid in network_uuids],
487                                    network_to_groups,
488                                    network_to_instances,
489                                    stats)
490
491   @staticmethod
492   def _GetStats(pool):
493     """Returns statistics for a network address pool.
494
495     """
496     return {
497       "free_count": pool.GetFreeCount(),
498       "reserved_count": pool.GetReservedCount(),
499       "map": pool.GetMap(),
500       "external_reservations":
501         utils.CommaJoin(pool.GetExternalReservations()),
502       }
503
504
505 class LUNetworkQuery(NoHooksLU):
506   """Logical unit for querying networks.
507
508   """
509   REQ_BGL = False
510
511   def CheckArguments(self):
512     self.nq = NetworkQuery(qlang.MakeSimpleFilter("name", self.op.names),
513                             self.op.output_fields, self.op.use_locking)
514
515   def ExpandNames(self):
516     self.nq.ExpandNames(self)
517
518   def Exec(self, feedback_fn):
519     return self.nq.OldStyleQuery(self)
520
521
522 def _FmtNetworkConflict(details):
523   """Utility for L{_NetworkConflictCheck}.
524
525   """
526   return utils.CommaJoin("nic%s/%s" % (idx, ipaddr)
527                          for (idx, ipaddr) in details)
528
529
530 def _NetworkConflictCheck(lu, check_fn, action, instances):
531   """Checks for network interface conflicts with a network.
532
533   @type lu: L{LogicalUnit}
534   @type check_fn: callable receiving one parameter (L{objects.NIC}) and
535     returning boolean
536   @param check_fn: Function checking for conflict
537   @type action: string
538   @param action: Part of error message (see code)
539   @raise errors.OpPrereqError: If conflicting IP addresses are found.
540
541   """
542   conflicts = []
543
544   for (_, instance) in lu.cfg.GetMultiInstanceInfo(instances):
545     instconflicts = [(idx, nic.ip)
546                      for (idx, nic) in enumerate(instance.nics)
547                      if check_fn(nic)]
548
549     if instconflicts:
550       conflicts.append((instance.name, instconflicts))
551
552   if conflicts:
553     lu.LogWarning("IP addresses from network '%s', which is about to %s"
554                   " node group '%s', are in use: %s" %
555                   (lu.network_name, action, lu.group.name,
556                    utils.CommaJoin(("%s: %s" %
557                                     (name, _FmtNetworkConflict(details)))
558                                    for (name, details) in conflicts)))
559
560     raise errors.OpPrereqError("Conflicting IP addresses found; "
561                                " remove/modify the corresponding network"
562                                " interfaces", errors.ECODE_STATE)
563
564
565 class LUNetworkConnect(LogicalUnit):
566   """Connect a network to a nodegroup
567
568   """
569   HPATH = "network-connect"
570   HTYPE = constants.HTYPE_NETWORK
571   REQ_BGL = False
572
573   def ExpandNames(self):
574     self.network_name = self.op.network_name
575     self.group_name = self.op.group_name
576     self.network_mode = self.op.network_mode
577     self.network_link = self.op.network_link
578
579     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
580     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
581
582     self.needed_locks = {
583       locking.LEVEL_NODEGROUP: [self.group_uuid],
584       }
585
586     if self.op.conflicts_check:
587       self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
588       self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid]
589       self.share_locks[locking.LEVEL_NETWORK] = 1
590       self.share_locks[locking.LEVEL_INSTANCE] = 1
591
592   def DeclareLocks(self, level):
593     pass
594
595   def BuildHooksEnv(self):
596     ret = {
597       "GROUP_NAME": self.group_name,
598       "GROUP_NETWORK_MODE": self.network_mode,
599       "GROUP_NETWORK_LINK": self.network_link,
600       }
601     return ret
602
603   def BuildHooksNodes(self):
604     nodes = self.cfg.GetNodeGroup(self.group_uuid).members
605     return (nodes, nodes)
606
607   def CheckPrereq(self):
608     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
609
610     assert self.group_uuid in owned_groups
611
612     # Check if locked instances are still correct
613     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
614
615     self.netparams = {
616       constants.NIC_MODE: self.network_mode,
617       constants.NIC_LINK: self.network_link,
618       }
619     objects.NIC.CheckParameterSyntax(self.netparams)
620
621     self.group = self.cfg.GetNodeGroup(self.group_uuid)
622     #if self.network_mode == constants.NIC_MODE_BRIDGED:
623     #  _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid)
624     self.connected = False
625     if self.network_uuid in self.group.networks:
626       self.LogWarning("Network '%s' is already mapped to group '%s'" %
627                       (self.network_name, self.group.name))
628       self.connected = True
629
630     # check only if not already connected
631     elif self.op.conflicts_check:
632       pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid))
633
634       _NetworkConflictCheck(self, lambda nic: pool.Contains(nic.ip),
635                             "connect to", owned_instances)
636
637   def Exec(self, feedback_fn):
638     # Connect the network and update the group only if not already connected
639     if not self.connected:
640       self.group.networks[self.network_uuid] = self.netparams
641       self.cfg.Update(self.group, feedback_fn)
642
643
644 class LUNetworkDisconnect(LogicalUnit):
645   """Disconnect a network to a nodegroup
646
647   """
648   HPATH = "network-disconnect"
649   HTYPE = constants.HTYPE_NETWORK
650   REQ_BGL = False
651
652   def ExpandNames(self):
653     self.network_name = self.op.network_name
654     self.group_name = self.op.group_name
655
656     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
657     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
658
659     self.needed_locks = {
660       locking.LEVEL_INSTANCE: locking.ALL_SET,
661       locking.LEVEL_NODEGROUP: [self.group_uuid],
662       }
663     self.share_locks[locking.LEVEL_INSTANCE] = 1
664
665   def DeclareLocks(self, level):
666     pass
667
668   def BuildHooksEnv(self):
669     ret = {
670       "GROUP_NAME": self.group_name,
671       }
672     return ret
673
674   def BuildHooksNodes(self):
675     nodes = self.cfg.GetNodeGroup(self.group_uuid).members
676     return (nodes, nodes)
677
678   def CheckPrereq(self):
679     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
680
681     assert self.group_uuid in owned_groups
682
683     # Check if locked instances are still correct
684     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
685
686     self.group = self.cfg.GetNodeGroup(self.group_uuid)
687     self.connected = True
688     if self.network_uuid not in self.group.networks:
689       self.LogWarning("Network '%s' is not mapped to group '%s'",
690                       self.network_name, self.group.name)
691       self.connected = False
692
693     # We need this check only if network is not already connected
694     else:
695       _NetworkConflictCheck(self, lambda nic: nic.network == self.network_uuid,
696                             "disconnect from", owned_instances)
697
698   def Exec(self, feedback_fn):
699     # Disconnect the network and update the group only if network is connected
700     if self.connected:
701       del self.group.networks[self.network_uuid]
702       self.cfg.Update(self.group, feedback_fn)