New RPC to get size and spindles of disks
[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, CheckNodeGroupInstances
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)
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)
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_INSTANCE: [],
584       locking.LEVEL_NODEGROUP: [self.group_uuid],
585       }
586     self.share_locks[locking.LEVEL_INSTANCE] = 1
587
588     if self.op.conflicts_check:
589       self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid]
590       self.share_locks[locking.LEVEL_NETWORK] = 1
591
592   def DeclareLocks(self, level):
593     if level == locking.LEVEL_INSTANCE:
594       assert not self.needed_locks[locking.LEVEL_INSTANCE]
595
596       # Lock instances optimistically, needs verification once group lock has
597       # been acquired
598       if self.op.conflicts_check:
599         self.needed_locks[locking.LEVEL_INSTANCE] = \
600             self.cfg.GetNodeGroupInstances(self.group_uuid)
601
602   def BuildHooksEnv(self):
603     ret = {
604       "GROUP_NAME": self.group_name,
605       "GROUP_NETWORK_MODE": self.network_mode,
606       "GROUP_NETWORK_LINK": self.network_link,
607       }
608     return ret
609
610   def BuildHooksNodes(self):
611     nodes = self.cfg.GetNodeGroup(self.group_uuid).members
612     return (nodes, nodes)
613
614   def CheckPrereq(self):
615     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
616
617     assert self.group_uuid in owned_groups
618
619     # Check if locked instances are still correct
620     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
621     if self.op.conflicts_check:
622       CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
623
624     self.netparams = {
625       constants.NIC_MODE: self.network_mode,
626       constants.NIC_LINK: self.network_link,
627       }
628     objects.NIC.CheckParameterSyntax(self.netparams)
629
630     self.group = self.cfg.GetNodeGroup(self.group_uuid)
631     #if self.network_mode == constants.NIC_MODE_BRIDGED:
632     #  _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid)
633     self.connected = False
634     if self.network_uuid in self.group.networks:
635       self.LogWarning("Network '%s' is already mapped to group '%s'" %
636                       (self.network_name, self.group.name))
637       self.connected = True
638
639     # check only if not already connected
640     elif self.op.conflicts_check:
641       pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid))
642
643       _NetworkConflictCheck(self, lambda nic: pool.Contains(nic.ip),
644                             "connect to", owned_instances)
645
646   def Exec(self, feedback_fn):
647     # Connect the network and update the group only if not already connected
648     if not self.connected:
649       self.group.networks[self.network_uuid] = self.netparams
650       self.cfg.Update(self.group, feedback_fn)
651
652
653 class LUNetworkDisconnect(LogicalUnit):
654   """Disconnect a network to a nodegroup
655
656   """
657   HPATH = "network-disconnect"
658   HTYPE = constants.HTYPE_NETWORK
659   REQ_BGL = False
660
661   def ExpandNames(self):
662     self.network_name = self.op.network_name
663     self.group_name = self.op.group_name
664
665     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
666     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
667
668     self.needed_locks = {
669       locking.LEVEL_INSTANCE: [],
670       locking.LEVEL_NODEGROUP: [self.group_uuid],
671       }
672     self.share_locks[locking.LEVEL_INSTANCE] = 1
673
674   def DeclareLocks(self, level):
675     if level == locking.LEVEL_INSTANCE:
676       assert not self.needed_locks[locking.LEVEL_INSTANCE]
677
678       # Lock instances optimistically, needs verification once group lock has
679       # been acquired
680       self.needed_locks[locking.LEVEL_INSTANCE] = \
681         self.cfg.GetNodeGroupInstances(self.group_uuid)
682
683   def BuildHooksEnv(self):
684     ret = {
685       "GROUP_NAME": self.group_name,
686       }
687     return ret
688
689   def BuildHooksNodes(self):
690     nodes = self.cfg.GetNodeGroup(self.group_uuid).members
691     return (nodes, nodes)
692
693   def CheckPrereq(self):
694     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
695
696     assert self.group_uuid in owned_groups
697
698     # Check if locked instances are still correct
699     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
700     CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
701
702     self.group = self.cfg.GetNodeGroup(self.group_uuid)
703     self.connected = True
704     if self.network_uuid not in self.group.networks:
705       self.LogWarning("Network '%s' is not mapped to group '%s'",
706                       self.network_name, self.group.name)
707       self.connected = False
708
709     # We need this check only if network is not already connected
710     else:
711       _NetworkConflictCheck(self, lambda nic: nic.network == self.network_uuid,
712                             "disconnect from", owned_instances)
713
714   def Exec(self, feedback_fn):
715     # Disconnect the network and update the group only if network is connected
716     if self.connected:
717       del self.group.networks[self.network_uuid]
718       self.cfg.Update(self.group, feedback_fn)