Display node name instead of UUID in error message
[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.uuid)
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   @param instances: the instances to check
540   @type instances: list of instance objects
541   @raise errors.OpPrereqError: If conflicting IP addresses are found.
542
543   """
544   conflicts = []
545
546   for instance in instances:
547     instconflicts = [(idx, nic.ip)
548                      for (idx, nic) in enumerate(instance.nics)
549                      if check_fn(nic)]
550
551     if instconflicts:
552       conflicts.append((instance.name, instconflicts))
553
554   if conflicts:
555     lu.LogWarning("IP addresses from network '%s', which is about to %s"
556                   " node group '%s', are in use: %s" %
557                   (lu.network_name, action, lu.group.name,
558                    utils.CommaJoin(("%s: %s" %
559                                     (name, _FmtNetworkConflict(details)))
560                                    for (name, details) in conflicts)))
561
562     raise errors.OpPrereqError("Conflicting IP addresses found; "
563                                " remove/modify the corresponding network"
564                                " interfaces", errors.ECODE_STATE)
565
566
567 class LUNetworkConnect(LogicalUnit):
568   """Connect a network to a nodegroup
569
570   """
571   HPATH = "network-connect"
572   HTYPE = constants.HTYPE_NETWORK
573   REQ_BGL = False
574
575   def ExpandNames(self):
576     self.network_name = self.op.network_name
577     self.group_name = self.op.group_name
578     self.network_mode = self.op.network_mode
579     self.network_link = self.op.network_link
580
581     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
582     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
583
584     self.needed_locks = {
585       locking.LEVEL_INSTANCE: [],
586       locking.LEVEL_NODEGROUP: [self.group_uuid],
587       }
588     self.share_locks[locking.LEVEL_INSTANCE] = 1
589
590     if self.op.conflicts_check:
591       self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid]
592       self.share_locks[locking.LEVEL_NETWORK] = 1
593
594   def DeclareLocks(self, level):
595     if level == locking.LEVEL_INSTANCE:
596       assert not self.needed_locks[locking.LEVEL_INSTANCE]
597
598       # Lock instances optimistically, needs verification once group lock has
599       # been acquired
600       if self.op.conflicts_check:
601         self.needed_locks[locking.LEVEL_INSTANCE] = \
602           self.cfg.GetInstanceNames(
603             self.cfg.GetNodeGroupInstances(self.group_uuid))
604
605   def BuildHooksEnv(self):
606     ret = {
607       "GROUP_NAME": self.group_name,
608       "GROUP_NETWORK_MODE": self.network_mode,
609       "GROUP_NETWORK_LINK": self.network_link,
610       }
611     return ret
612
613   def BuildHooksNodes(self):
614     node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
615     return (node_uuids, node_uuids)
616
617   def CheckPrereq(self):
618     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
619
620     assert self.group_uuid in owned_groups
621
622     # Check if locked instances are still correct
623     owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
624     if self.op.conflicts_check:
625       CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
626
627     self.netparams = {
628       constants.NIC_MODE: self.network_mode,
629       constants.NIC_LINK: self.network_link,
630       }
631     objects.NIC.CheckParameterSyntax(self.netparams)
632
633     self.group = self.cfg.GetNodeGroup(self.group_uuid)
634     #if self.network_mode == constants.NIC_MODE_BRIDGED:
635     #  _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid)
636     self.connected = False
637     if self.network_uuid in self.group.networks:
638       self.LogWarning("Network '%s' is already mapped to group '%s'" %
639                       (self.network_name, self.group.name))
640       self.connected = True
641
642     # check only if not already connected
643     elif self.op.conflicts_check:
644       pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid))
645
646       _NetworkConflictCheck(
647         self, lambda nic: pool.Contains(nic.ip), "connect to",
648         [instance_info for (_, instance_info) in
649          self.cfg.GetMultiInstanceInfoByName(owned_instance_names)])
650
651   def Exec(self, feedback_fn):
652     # Connect the network and update the group only if not already connected
653     if not self.connected:
654       self.group.networks[self.network_uuid] = self.netparams
655       self.cfg.Update(self.group, feedback_fn)
656
657
658 class LUNetworkDisconnect(LogicalUnit):
659   """Disconnect a network to a nodegroup
660
661   """
662   HPATH = "network-disconnect"
663   HTYPE = constants.HTYPE_NETWORK
664   REQ_BGL = False
665
666   def ExpandNames(self):
667     self.network_name = self.op.network_name
668     self.group_name = self.op.group_name
669
670     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
671     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
672
673     self.needed_locks = {
674       locking.LEVEL_INSTANCE: [],
675       locking.LEVEL_NODEGROUP: [self.group_uuid],
676       }
677     self.share_locks[locking.LEVEL_INSTANCE] = 1
678
679   def DeclareLocks(self, level):
680     if level == locking.LEVEL_INSTANCE:
681       assert not self.needed_locks[locking.LEVEL_INSTANCE]
682
683       # Lock instances optimistically, needs verification once group lock has
684       # been acquired
685       self.needed_locks[locking.LEVEL_INSTANCE] = \
686         self.cfg.GetInstanceNames(
687           self.cfg.GetNodeGroupInstances(self.group_uuid))
688
689   def BuildHooksEnv(self):
690     ret = {
691       "GROUP_NAME": self.group_name,
692       }
693     return ret
694
695   def BuildHooksNodes(self):
696     nodes = self.cfg.GetNodeGroup(self.group_uuid).members
697     return (nodes, nodes)
698
699   def CheckPrereq(self):
700     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
701
702     assert self.group_uuid in owned_groups
703
704     # Check if locked instances are still correct
705     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
706     CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
707
708     self.group = self.cfg.GetNodeGroup(self.group_uuid)
709     self.connected = True
710     if self.network_uuid not in self.group.networks:
711       self.LogWarning("Network '%s' is not mapped to group '%s'",
712                       self.network_name, self.group.name)
713       self.connected = False
714
715     # We need this check only if network is not already connected
716     else:
717       _NetworkConflictCheck(
718         self, lambda nic: nic.network == self.network_uuid, "disconnect from",
719         [instance_info for (_, instance_info) in
720         self.cfg.GetMultiInstanceInfoByName(owned_instances)])
721
722   def Exec(self, feedback_fn):
723     # Disconnect the network and update the group only if network is connected
724     if self.connected:
725       del self.group.networks[self.network_uuid]
726       self.cfg.Update(self.group, feedback_fn)