Remove physical_id field from disk object
[ganeti-local] / lib / config.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 """Configuration management for Ganeti
23
24 This module provides the interface to the Ganeti cluster configuration.
25
26 The configuration data is stored on every node but is updated on the master
27 only. After each update, the master distributes the data to the other nodes.
28
29 Currently, the data storage format is JSON. YAML was slow and consuming too
30 much memory.
31
32 """
33
34 # pylint: disable=R0904
35 # R0904: Too many public methods
36
37 import copy
38 import os
39 import random
40 import logging
41 import time
42 import itertools
43
44 from ganeti import errors
45 from ganeti import locking
46 from ganeti import utils
47 from ganeti import constants
48 from ganeti import rpc
49 from ganeti import objects
50 from ganeti import serializer
51 from ganeti import uidpool
52 from ganeti import netutils
53 from ganeti import runtime
54 from ganeti import pathutils
55 from ganeti import network
56
57
58 _config_lock = locking.SharedLock("ConfigWriter")
59
60 # job id used for resource management at config upgrade time
61 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
62
63
64 def _ValidateConfig(data):
65   """Verifies that a configuration objects looks valid.
66
67   This only verifies the version of the configuration.
68
69   @raise errors.ConfigurationError: if the version differs from what
70       we expect
71
72   """
73   if data.version != constants.CONFIG_VERSION:
74     raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
75
76
77 class TemporaryReservationManager:
78   """A temporary resource reservation manager.
79
80   This is used to reserve resources in a job, before using them, making sure
81   other jobs cannot get them in the meantime.
82
83   """
84   def __init__(self):
85     self._ec_reserved = {}
86
87   def Reserved(self, resource):
88     for holder_reserved in self._ec_reserved.values():
89       if resource in holder_reserved:
90         return True
91     return False
92
93   def Reserve(self, ec_id, resource):
94     if self.Reserved(resource):
95       raise errors.ReservationError("Duplicate reservation for resource '%s'"
96                                     % str(resource))
97     if ec_id not in self._ec_reserved:
98       self._ec_reserved[ec_id] = set([resource])
99     else:
100       self._ec_reserved[ec_id].add(resource)
101
102   def DropECReservations(self, ec_id):
103     if ec_id in self._ec_reserved:
104       del self._ec_reserved[ec_id]
105
106   def GetReserved(self):
107     all_reserved = set()
108     for holder_reserved in self._ec_reserved.values():
109       all_reserved.update(holder_reserved)
110     return all_reserved
111
112   def GetECReserved(self, ec_id):
113     """ Used when you want to retrieve all reservations for a specific
114         execution context. E.g when commiting reserved IPs for a specific
115         network.
116
117     """
118     ec_reserved = set()
119     if ec_id in self._ec_reserved:
120       ec_reserved.update(self._ec_reserved[ec_id])
121     return ec_reserved
122
123   def Generate(self, existing, generate_one_fn, ec_id):
124     """Generate a new resource of this type
125
126     """
127     assert callable(generate_one_fn)
128
129     all_elems = self.GetReserved()
130     all_elems.update(existing)
131     retries = 64
132     while retries > 0:
133       new_resource = generate_one_fn()
134       if new_resource is not None and new_resource not in all_elems:
135         break
136     else:
137       raise errors.ConfigurationError("Not able generate new resource"
138                                       " (last tried: %s)" % new_resource)
139     self.Reserve(ec_id, new_resource)
140     return new_resource
141
142
143 def _MatchNameComponentIgnoreCase(short_name, names):
144   """Wrapper around L{utils.text.MatchNameComponent}.
145
146   """
147   return utils.MatchNameComponent(short_name, names, case_sensitive=False)
148
149
150 def _CheckInstanceDiskIvNames(disks):
151   """Checks if instance's disks' C{iv_name} attributes are in order.
152
153   @type disks: list of L{objects.Disk}
154   @param disks: List of disks
155   @rtype: list of tuples; (int, string, string)
156   @return: List of wrongly named disks, each tuple contains disk index,
157     expected and actual name
158
159   """
160   result = []
161
162   for (idx, disk) in enumerate(disks):
163     exp_iv_name = "disk/%s" % idx
164     if disk.iv_name != exp_iv_name:
165       result.append((idx, exp_iv_name, disk.iv_name))
166
167   return result
168
169
170 class ConfigWriter(object):
171   """The interface to the cluster configuration.
172
173   @ivar _temporary_lvs: reservation manager for temporary LVs
174   @ivar _all_rms: a list of all temporary reservation managers
175
176   """
177   def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
178                accept_foreign=False):
179     self.write_count = 0
180     self._lock = _config_lock
181     self._config_data = None
182     self._offline = offline
183     if cfg_file is None:
184       self._cfg_file = pathutils.CLUSTER_CONF_FILE
185     else:
186       self._cfg_file = cfg_file
187     self._getents = _getents
188     self._temporary_ids = TemporaryReservationManager()
189     self._temporary_drbds = {}
190     self._temporary_macs = TemporaryReservationManager()
191     self._temporary_secrets = TemporaryReservationManager()
192     self._temporary_lvs = TemporaryReservationManager()
193     self._temporary_ips = TemporaryReservationManager()
194     self._all_rms = [self._temporary_ids, self._temporary_macs,
195                      self._temporary_secrets, self._temporary_lvs,
196                      self._temporary_ips]
197     # Note: in order to prevent errors when resolving our name in
198     # _DistributeConfig, we compute it here once and reuse it; it's
199     # better to raise an error before starting to modify the config
200     # file than after it was modified
201     self._my_hostname = netutils.Hostname.GetSysName()
202     self._last_cluster_serial = -1
203     self._cfg_id = None
204     self._context = None
205     self._OpenConfig(accept_foreign)
206
207   def _GetRpc(self, address_list):
208     """Returns RPC runner for configuration.
209
210     """
211     return rpc.ConfigRunner(self._context, address_list)
212
213   def SetContext(self, context):
214     """Sets Ganeti context.
215
216     """
217     self._context = context
218
219   # this method needs to be static, so that we can call it on the class
220   @staticmethod
221   def IsCluster():
222     """Check if the cluster is configured.
223
224     """
225     return os.path.exists(pathutils.CLUSTER_CONF_FILE)
226
227   @locking.ssynchronized(_config_lock, shared=1)
228   def GetNdParams(self, node):
229     """Get the node params populated with cluster defaults.
230
231     @type node: L{objects.Node}
232     @param node: The node we want to know the params for
233     @return: A dict with the filled in node params
234
235     """
236     nodegroup = self._UnlockedGetNodeGroup(node.group)
237     return self._config_data.cluster.FillND(node, nodegroup)
238
239   @locking.ssynchronized(_config_lock, shared=1)
240   def GetInstanceDiskParams(self, instance):
241     """Get the disk params populated with inherit chain.
242
243     @type instance: L{objects.Instance}
244     @param instance: The instance we want to know the params for
245     @return: A dict with the filled in disk params
246
247     """
248     node = self._UnlockedGetNodeInfo(instance.primary_node)
249     nodegroup = self._UnlockedGetNodeGroup(node.group)
250     return self._UnlockedGetGroupDiskParams(nodegroup)
251
252   @locking.ssynchronized(_config_lock, shared=1)
253   def GetGroupDiskParams(self, group):
254     """Get the disk params populated with inherit chain.
255
256     @type group: L{objects.NodeGroup}
257     @param group: The group we want to know the params for
258     @return: A dict with the filled in disk params
259
260     """
261     return self._UnlockedGetGroupDiskParams(group)
262
263   def _UnlockedGetGroupDiskParams(self, group):
264     """Get the disk params populated with inherit chain down to node-group.
265
266     @type group: L{objects.NodeGroup}
267     @param group: The group we want to know the params for
268     @return: A dict with the filled in disk params
269
270     """
271     return self._config_data.cluster.SimpleFillDP(group.diskparams)
272
273   def _UnlockedGetNetworkMACPrefix(self, net_uuid):
274     """Return the network mac prefix if it exists or the cluster level default.
275
276     """
277     prefix = None
278     if net_uuid:
279       nobj = self._UnlockedGetNetwork(net_uuid)
280       if nobj.mac_prefix:
281         prefix = nobj.mac_prefix
282
283     return prefix
284
285   def _GenerateOneMAC(self, prefix=None):
286     """Return a function that randomly generates a MAC suffic
287        and appends it to the given prefix. If prefix is not given get
288        the cluster level default.
289
290     """
291     if not prefix:
292       prefix = self._config_data.cluster.mac_prefix
293
294     def GenMac():
295       byte1 = random.randrange(0, 256)
296       byte2 = random.randrange(0, 256)
297       byte3 = random.randrange(0, 256)
298       mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
299       return mac
300
301     return GenMac
302
303   @locking.ssynchronized(_config_lock, shared=1)
304   def GenerateMAC(self, net_uuid, ec_id):
305     """Generate a MAC for an instance.
306
307     This should check the current instances for duplicates.
308
309     """
310     existing = self._AllMACs()
311     prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
312     gen_mac = self._GenerateOneMAC(prefix)
313     return self._temporary_ids.Generate(existing, gen_mac, ec_id)
314
315   @locking.ssynchronized(_config_lock, shared=1)
316   def ReserveMAC(self, mac, ec_id):
317     """Reserve a MAC for an instance.
318
319     This only checks instances managed by this cluster, it does not
320     check for potential collisions elsewhere.
321
322     """
323     all_macs = self._AllMACs()
324     if mac in all_macs:
325       raise errors.ReservationError("mac already in use")
326     else:
327       self._temporary_macs.Reserve(ec_id, mac)
328
329   def _UnlockedCommitTemporaryIps(self, ec_id):
330     """Commit all reserved IP address to their respective pools
331
332     """
333     for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
334       self._UnlockedCommitIp(action, net_uuid, address)
335
336   def _UnlockedCommitIp(self, action, net_uuid, address):
337     """Commit a reserved IP address to an IP pool.
338
339     The IP address is taken from the network's IP pool and marked as reserved.
340
341     """
342     nobj = self._UnlockedGetNetwork(net_uuid)
343     pool = network.AddressPool(nobj)
344     if action == constants.RESERVE_ACTION:
345       pool.Reserve(address)
346     elif action == constants.RELEASE_ACTION:
347       pool.Release(address)
348
349   def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
350     """Give a specific IP address back to an IP pool.
351
352     The IP address is returned to the IP pool designated by pool_id and marked
353     as reserved.
354
355     """
356     self._temporary_ips.Reserve(ec_id,
357                                 (constants.RELEASE_ACTION, address, net_uuid))
358
359   @locking.ssynchronized(_config_lock, shared=1)
360   def ReleaseIp(self, net_uuid, address, ec_id):
361     """Give a specified IP address back to an IP pool.
362
363     This is just a wrapper around _UnlockedReleaseIp.
364
365     """
366     if net_uuid:
367       self._UnlockedReleaseIp(net_uuid, address, ec_id)
368
369   @locking.ssynchronized(_config_lock, shared=1)
370   def GenerateIp(self, net_uuid, ec_id):
371     """Find a free IPv4 address for an instance.
372
373     """
374     nobj = self._UnlockedGetNetwork(net_uuid)
375     pool = network.AddressPool(nobj)
376
377     def gen_one():
378       try:
379         ip = pool.GenerateFree()
380       except errors.AddressPoolError:
381         raise errors.ReservationError("Cannot generate IP. Network is full")
382       return (constants.RESERVE_ACTION, ip, net_uuid)
383
384     _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
385     return address
386
387   def _UnlockedReserveIp(self, net_uuid, address, ec_id):
388     """Reserve a given IPv4 address for use by an instance.
389
390     """
391     nobj = self._UnlockedGetNetwork(net_uuid)
392     pool = network.AddressPool(nobj)
393     try:
394       isreserved = pool.IsReserved(address)
395     except errors.AddressPoolError:
396       raise errors.ReservationError("IP address not in network")
397     if isreserved:
398       raise errors.ReservationError("IP address already in use")
399
400     return self._temporary_ips.Reserve(ec_id,
401                                        (constants.RESERVE_ACTION,
402                                         address, net_uuid))
403
404   @locking.ssynchronized(_config_lock, shared=1)
405   def ReserveIp(self, net_uuid, address, ec_id):
406     """Reserve a given IPv4 address for use by an instance.
407
408     """
409     if net_uuid:
410       return self._UnlockedReserveIp(net_uuid, address, ec_id)
411
412   @locking.ssynchronized(_config_lock, shared=1)
413   def ReserveLV(self, lv_name, ec_id):
414     """Reserve an VG/LV pair for an instance.
415
416     @type lv_name: string
417     @param lv_name: the logical volume name to reserve
418
419     """
420     all_lvs = self._AllLVs()
421     if lv_name in all_lvs:
422       raise errors.ReservationError("LV already in use")
423     else:
424       self._temporary_lvs.Reserve(ec_id, lv_name)
425
426   @locking.ssynchronized(_config_lock, shared=1)
427   def GenerateDRBDSecret(self, ec_id):
428     """Generate a DRBD secret.
429
430     This checks the current disks for duplicates.
431
432     """
433     return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
434                                             utils.GenerateSecret,
435                                             ec_id)
436
437   def _AllLVs(self):
438     """Compute the list of all LVs.
439
440     """
441     lvnames = set()
442     for instance in self._config_data.instances.values():
443       node_data = instance.MapLVsByNode()
444       for lv_list in node_data.values():
445         lvnames.update(lv_list)
446     return lvnames
447
448   def _AllDisks(self):
449     """Compute the list of all Disks (recursively, including children).
450
451     """
452     def DiskAndAllChildren(disk):
453       """Returns a list containing the given disk and all of his children.
454
455       """
456       disks = [disk]
457       if disk.children:
458         for child_disk in disk.children:
459           disks.extend(DiskAndAllChildren(child_disk))
460       return disks
461
462     disks = []
463     for instance in self._config_data.instances.values():
464       for disk in instance.disks:
465         disks.extend(DiskAndAllChildren(disk))
466     return disks
467
468   def _AllNICs(self):
469     """Compute the list of all NICs.
470
471     """
472     nics = []
473     for instance in self._config_data.instances.values():
474       nics.extend(instance.nics)
475     return nics
476
477   def _AllIDs(self, include_temporary):
478     """Compute the list of all UUIDs and names we have.
479
480     @type include_temporary: boolean
481     @param include_temporary: whether to include the _temporary_ids set
482     @rtype: set
483     @return: a set of IDs
484
485     """
486     existing = set()
487     if include_temporary:
488       existing.update(self._temporary_ids.GetReserved())
489     existing.update(self._AllLVs())
490     existing.update(self._config_data.instances.keys())
491     existing.update(self._config_data.nodes.keys())
492     existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
493     return existing
494
495   def _GenerateUniqueID(self, ec_id):
496     """Generate an unique UUID.
497
498     This checks the current node, instances and disk names for
499     duplicates.
500
501     @rtype: string
502     @return: the unique id
503
504     """
505     existing = self._AllIDs(include_temporary=False)
506     return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
507
508   @locking.ssynchronized(_config_lock, shared=1)
509   def GenerateUniqueID(self, ec_id):
510     """Generate an unique ID.
511
512     This is just a wrapper over the unlocked version.
513
514     @type ec_id: string
515     @param ec_id: unique id for the job to reserve the id to
516
517     """
518     return self._GenerateUniqueID(ec_id)
519
520   def _AllMACs(self):
521     """Return all MACs present in the config.
522
523     @rtype: list
524     @return: the list of all MACs
525
526     """
527     result = []
528     for instance in self._config_data.instances.values():
529       for nic in instance.nics:
530         result.append(nic.mac)
531
532     return result
533
534   def _AllDRBDSecrets(self):
535     """Return all DRBD secrets present in the config.
536
537     @rtype: list
538     @return: the list of all DRBD secrets
539
540     """
541     def helper(disk, result):
542       """Recursively gather secrets from this disk."""
543       if disk.dev_type == constants.DT_DRBD8:
544         result.append(disk.logical_id[5])
545       if disk.children:
546         for child in disk.children:
547           helper(child, result)
548
549     result = []
550     for instance in self._config_data.instances.values():
551       for disk in instance.disks:
552         helper(disk, result)
553
554     return result
555
556   def _CheckDiskIDs(self, disk, l_ids):
557     """Compute duplicate disk IDs
558
559     @type disk: L{objects.Disk}
560     @param disk: the disk at which to start searching
561     @type l_ids: list
562     @param l_ids: list of current logical ids
563     @rtype: list
564     @return: a list of error messages
565
566     """
567     result = []
568     if disk.logical_id is not None:
569       if disk.logical_id in l_ids:
570         result.append("duplicate logical id %s" % str(disk.logical_id))
571       else:
572         l_ids.append(disk.logical_id)
573
574     if disk.children:
575       for child in disk.children:
576         result.extend(self._CheckDiskIDs(child, l_ids))
577     return result
578
579   def _UnlockedVerifyConfig(self):
580     """Verify function.
581
582     @rtype: list
583     @return: a list of error messages; a non-empty list signifies
584         configuration errors
585
586     """
587     # pylint: disable=R0914
588     result = []
589     seen_macs = []
590     ports = {}
591     data = self._config_data
592     cluster = data.cluster
593     seen_lids = []
594
595     # global cluster checks
596     if not cluster.enabled_hypervisors:
597       result.append("enabled hypervisors list doesn't have any entries")
598     invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
599     if invalid_hvs:
600       result.append("enabled hypervisors contains invalid entries: %s" %
601                     utils.CommaJoin(invalid_hvs))
602     missing_hvp = (set(cluster.enabled_hypervisors) -
603                    set(cluster.hvparams.keys()))
604     if missing_hvp:
605       result.append("hypervisor parameters missing for the enabled"
606                     " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
607
608     if not cluster.enabled_disk_templates:
609       result.append("enabled disk templates list doesn't have any entries")
610     invalid_disk_templates = set(cluster.enabled_disk_templates) \
611                                - constants.DISK_TEMPLATES
612     if invalid_disk_templates:
613       result.append("enabled disk templates list contains invalid entries:"
614                     " %s" % utils.CommaJoin(invalid_disk_templates))
615
616     if cluster.master_node not in data.nodes:
617       result.append("cluster has invalid primary node '%s'" %
618                     cluster.master_node)
619
620     def _helper(owner, attr, value, template):
621       try:
622         utils.ForceDictType(value, template)
623       except errors.GenericError, err:
624         result.append("%s has invalid %s: %s" % (owner, attr, err))
625
626     def _helper_nic(owner, params):
627       try:
628         objects.NIC.CheckParameterSyntax(params)
629       except errors.ConfigurationError, err:
630         result.append("%s has invalid nicparams: %s" % (owner, err))
631
632     def _helper_ipolicy(owner, ipolicy, iscluster):
633       try:
634         objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
635       except errors.ConfigurationError, err:
636         result.append("%s has invalid instance policy: %s" % (owner, err))
637       for key, value in ipolicy.items():
638         if key == constants.ISPECS_MINMAX:
639           for k in range(len(value)):
640             _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
641         elif key == constants.ISPECS_STD:
642           _helper(owner, "ipolicy/" + key, value,
643                   constants.ISPECS_PARAMETER_TYPES)
644         else:
645           # FIXME: assuming list type
646           if key in constants.IPOLICY_PARAMETERS:
647             exp_type = float
648           else:
649             exp_type = list
650           if not isinstance(value, exp_type):
651             result.append("%s has invalid instance policy: for %s,"
652                           " expecting %s, got %s" %
653                           (owner, key, exp_type.__name__, type(value)))
654
655     def _helper_ispecs(owner, parentkey, params):
656       for (key, value) in params.items():
657         fullkey = "/".join([parentkey, key])
658         _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
659
660     # check cluster parameters
661     _helper("cluster", "beparams", cluster.SimpleFillBE({}),
662             constants.BES_PARAMETER_TYPES)
663     _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
664             constants.NICS_PARAMETER_TYPES)
665     _helper_nic("cluster", cluster.SimpleFillNIC({}))
666     _helper("cluster", "ndparams", cluster.SimpleFillND({}),
667             constants.NDS_PARAMETER_TYPES)
668     _helper_ipolicy("cluster", cluster.ipolicy, True)
669
670     # per-instance checks
671     for instance_uuid in data.instances:
672       instance = data.instances[instance_uuid]
673       if instance.uuid != instance_uuid:
674         result.append("instance '%s' is indexed by wrong UUID '%s'" %
675                       (instance.name, instance_uuid))
676       if instance.primary_node not in data.nodes:
677         result.append("instance '%s' has invalid primary node '%s'" %
678                       (instance.name, instance.primary_node))
679       for snode in instance.secondary_nodes:
680         if snode not in data.nodes:
681           result.append("instance '%s' has invalid secondary node '%s'" %
682                         (instance.name, snode))
683       for idx, nic in enumerate(instance.nics):
684         if nic.mac in seen_macs:
685           result.append("instance '%s' has NIC %d mac %s duplicate" %
686                         (instance.name, idx, nic.mac))
687         else:
688           seen_macs.append(nic.mac)
689         if nic.nicparams:
690           filled = cluster.SimpleFillNIC(nic.nicparams)
691           owner = "instance %s nic %d" % (instance.name, idx)
692           _helper(owner, "nicparams",
693                   filled, constants.NICS_PARAMETER_TYPES)
694           _helper_nic(owner, filled)
695
696       # disk template checks
697       if not instance.disk_template in data.cluster.enabled_disk_templates:
698         result.append("instance '%s' uses the disabled disk template '%s'." %
699                       (instance.name, instance.disk_template))
700
701       # parameter checks
702       if instance.beparams:
703         _helper("instance %s" % instance.name, "beparams",
704                 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
705
706       # gather the drbd ports for duplicate checks
707       for (idx, dsk) in enumerate(instance.disks):
708         if dsk.dev_type in constants.LDS_DRBD:
709           tcp_port = dsk.logical_id[2]
710           if tcp_port not in ports:
711             ports[tcp_port] = []
712           ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
713       # gather network port reservation
714       net_port = getattr(instance, "network_port", None)
715       if net_port is not None:
716         if net_port not in ports:
717           ports[net_port] = []
718         ports[net_port].append((instance.name, "network port"))
719
720       # instance disk verify
721       for idx, disk in enumerate(instance.disks):
722         result.extend(["instance '%s' disk %d error: %s" %
723                        (instance.name, idx, msg) for msg in disk.Verify()])
724         result.extend(self._CheckDiskIDs(disk, seen_lids))
725
726       wrong_names = _CheckInstanceDiskIvNames(instance.disks)
727       if wrong_names:
728         tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
729                          (idx, exp_name, actual_name))
730                         for (idx, exp_name, actual_name) in wrong_names)
731
732         result.append("Instance '%s' has wrongly named disks: %s" %
733                       (instance.name, tmp))
734
735     # cluster-wide pool of free ports
736     for free_port in cluster.tcpudp_port_pool:
737       if free_port not in ports:
738         ports[free_port] = []
739       ports[free_port].append(("cluster", "port marked as free"))
740
741     # compute tcp/udp duplicate ports
742     keys = ports.keys()
743     keys.sort()
744     for pnum in keys:
745       pdata = ports[pnum]
746       if len(pdata) > 1:
747         txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
748         result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
749
750     # highest used tcp port check
751     if keys:
752       if keys[-1] > cluster.highest_used_port:
753         result.append("Highest used port mismatch, saved %s, computed %s" %
754                       (cluster.highest_used_port, keys[-1]))
755
756     if not data.nodes[cluster.master_node].master_candidate:
757       result.append("Master node is not a master candidate")
758
759     # master candidate checks
760     mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
761     if mc_now < mc_max:
762       result.append("Not enough master candidates: actual %d, target %d" %
763                     (mc_now, mc_max))
764
765     # node checks
766     for node_uuid, node in data.nodes.items():
767       if node.uuid != node_uuid:
768         result.append("Node '%s' is indexed by wrong UUID '%s'" %
769                       (node.name, node_uuid))
770       if [node.master_candidate, node.drained, node.offline].count(True) > 1:
771         result.append("Node %s state is invalid: master_candidate=%s,"
772                       " drain=%s, offline=%s" %
773                       (node.name, node.master_candidate, node.drained,
774                        node.offline))
775       if node.group not in data.nodegroups:
776         result.append("Node '%s' has invalid group '%s'" %
777                       (node.name, node.group))
778       else:
779         _helper("node %s" % node.name, "ndparams",
780                 cluster.FillND(node, data.nodegroups[node.group]),
781                 constants.NDS_PARAMETER_TYPES)
782       used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
783       if used_globals:
784         result.append("Node '%s' has some global parameters set: %s" %
785                       (node.name, utils.CommaJoin(used_globals)))
786
787     # nodegroups checks
788     nodegroups_names = set()
789     for nodegroup_uuid in data.nodegroups:
790       nodegroup = data.nodegroups[nodegroup_uuid]
791       if nodegroup.uuid != nodegroup_uuid:
792         result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
793                       % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
794       if utils.UUID_RE.match(nodegroup.name.lower()):
795         result.append("node group '%s' (uuid: '%s') has uuid-like name" %
796                       (nodegroup.name, nodegroup.uuid))
797       if nodegroup.name in nodegroups_names:
798         result.append("duplicate node group name '%s'" % nodegroup.name)
799       else:
800         nodegroups_names.add(nodegroup.name)
801       group_name = "group %s" % nodegroup.name
802       _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
803                       False)
804       if nodegroup.ndparams:
805         _helper(group_name, "ndparams",
806                 cluster.SimpleFillND(nodegroup.ndparams),
807                 constants.NDS_PARAMETER_TYPES)
808
809     # drbd minors check
810     _, duplicates = self._UnlockedComputeDRBDMap()
811     for node, minor, instance_a, instance_b in duplicates:
812       result.append("DRBD minor %d on node %s is assigned twice to instances"
813                     " %s and %s" % (minor, node, instance_a, instance_b))
814
815     # IP checks
816     default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
817     ips = {}
818
819     def _AddIpAddress(ip, name):
820       ips.setdefault(ip, []).append(name)
821
822     _AddIpAddress(cluster.master_ip, "cluster_ip")
823
824     for node in data.nodes.values():
825       _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
826       if node.secondary_ip != node.primary_ip:
827         _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
828
829     for instance in data.instances.values():
830       for idx, nic in enumerate(instance.nics):
831         if nic.ip is None:
832           continue
833
834         nicparams = objects.FillDict(default_nicparams, nic.nicparams)
835         nic_mode = nicparams[constants.NIC_MODE]
836         nic_link = nicparams[constants.NIC_LINK]
837
838         if nic_mode == constants.NIC_MODE_BRIDGED:
839           link = "bridge:%s" % nic_link
840         elif nic_mode == constants.NIC_MODE_ROUTED:
841           link = "route:%s" % nic_link
842         else:
843           raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
844
845         _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
846                       "instance:%s/nic:%d" % (instance.name, idx))
847
848     for ip, owners in ips.items():
849       if len(owners) > 1:
850         result.append("IP address %s is used by multiple owners: %s" %
851                       (ip, utils.CommaJoin(owners)))
852
853     return result
854
855   @locking.ssynchronized(_config_lock, shared=1)
856   def VerifyConfig(self):
857     """Verify function.
858
859     This is just a wrapper over L{_UnlockedVerifyConfig}.
860
861     @rtype: list
862     @return: a list of error messages; a non-empty list signifies
863         configuration errors
864
865     """
866     return self._UnlockedVerifyConfig()
867
868   @locking.ssynchronized(_config_lock)
869   def AddTcpUdpPort(self, port):
870     """Adds a new port to the available port pool.
871
872     @warning: this method does not "flush" the configuration (via
873         L{_WriteConfig}); callers should do that themselves once the
874         configuration is stable
875
876     """
877     if not isinstance(port, int):
878       raise errors.ProgrammerError("Invalid type passed for port")
879
880     self._config_data.cluster.tcpudp_port_pool.add(port)
881
882   @locking.ssynchronized(_config_lock, shared=1)
883   def GetPortList(self):
884     """Returns a copy of the current port list.
885
886     """
887     return self._config_data.cluster.tcpudp_port_pool.copy()
888
889   @locking.ssynchronized(_config_lock)
890   def AllocatePort(self):
891     """Allocate a port.
892
893     The port will be taken from the available port pool or from the
894     default port range (and in this case we increase
895     highest_used_port).
896
897     """
898     # If there are TCP/IP ports configured, we use them first.
899     if self._config_data.cluster.tcpudp_port_pool:
900       port = self._config_data.cluster.tcpudp_port_pool.pop()
901     else:
902       port = self._config_data.cluster.highest_used_port + 1
903       if port >= constants.LAST_DRBD_PORT:
904         raise errors.ConfigurationError("The highest used port is greater"
905                                         " than %s. Aborting." %
906                                         constants.LAST_DRBD_PORT)
907       self._config_data.cluster.highest_used_port = port
908
909     self._WriteConfig()
910     return port
911
912   def _UnlockedComputeDRBDMap(self):
913     """Compute the used DRBD minor/nodes.
914
915     @rtype: (dict, list)
916     @return: dictionary of node_uuid: dict of minor: instance_uuid;
917         the returned dict will have all the nodes in it (even if with
918         an empty list), and a list of duplicates; if the duplicates
919         list is not empty, the configuration is corrupted and its caller
920         should raise an exception
921
922     """
923     def _AppendUsedMinors(get_node_name_fn, instance, disk, used):
924       duplicates = []
925       if disk.dev_type == constants.DT_DRBD8 and len(disk.logical_id) >= 5:
926         node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
927         for node_uuid, minor in ((node_a, minor_a), (node_b, minor_b)):
928           assert node_uuid in used, \
929             ("Node '%s' of instance '%s' not found in node list" %
930              (get_node_name_fn(node_uuid), instance.name))
931           if minor in used[node_uuid]:
932             duplicates.append((node_uuid, minor, instance.uuid,
933                                used[node_uuid][minor]))
934           else:
935             used[node_uuid][minor] = instance.uuid
936       if disk.children:
937         for child in disk.children:
938           duplicates.extend(_AppendUsedMinors(get_node_name_fn, instance, child,
939                                               used))
940       return duplicates
941
942     duplicates = []
943     my_dict = dict((node_uuid, {}) for node_uuid in self._config_data.nodes)
944     for instance in self._config_data.instances.itervalues():
945       for disk in instance.disks:
946         duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
947                                             instance, disk, my_dict))
948     for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
949       if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
950         duplicates.append((node_uuid, minor, inst_uuid,
951                            my_dict[node_uuid][minor]))
952       else:
953         my_dict[node_uuid][minor] = inst_uuid
954     return my_dict, duplicates
955
956   @locking.ssynchronized(_config_lock)
957   def ComputeDRBDMap(self):
958     """Compute the used DRBD minor/nodes.
959
960     This is just a wrapper over L{_UnlockedComputeDRBDMap}.
961
962     @return: dictionary of node_uuid: dict of minor: instance_uuid;
963         the returned dict will have all the nodes in it (even if with
964         an empty list).
965
966     """
967     d_map, duplicates = self._UnlockedComputeDRBDMap()
968     if duplicates:
969       raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
970                                       str(duplicates))
971     return d_map
972
973   @locking.ssynchronized(_config_lock)
974   def AllocateDRBDMinor(self, node_uuids, inst_uuid):
975     """Allocate a drbd minor.
976
977     The free minor will be automatically computed from the existing
978     devices. A node can be given multiple times in order to allocate
979     multiple minors. The result is the list of minors, in the same
980     order as the passed nodes.
981
982     @type inst_uuid: string
983     @param inst_uuid: the instance for which we allocate minors
984
985     """
986     assert isinstance(inst_uuid, basestring), \
987            "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
988
989     d_map, duplicates = self._UnlockedComputeDRBDMap()
990     if duplicates:
991       raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
992                                       str(duplicates))
993     result = []
994     for nuuid in node_uuids:
995       ndata = d_map[nuuid]
996       if not ndata:
997         # no minors used, we can start at 0
998         result.append(0)
999         ndata[0] = inst_uuid
1000         self._temporary_drbds[(nuuid, 0)] = inst_uuid
1001         continue
1002       keys = ndata.keys()
1003       keys.sort()
1004       ffree = utils.FirstFree(keys)
1005       if ffree is None:
1006         # return the next minor
1007         # TODO: implement high-limit check
1008         minor = keys[-1] + 1
1009       else:
1010         minor = ffree
1011       # double-check minor against current instances
1012       assert minor not in d_map[nuuid], \
1013              ("Attempt to reuse allocated DRBD minor %d on node %s,"
1014               " already allocated to instance %s" %
1015               (minor, nuuid, d_map[nuuid][minor]))
1016       ndata[minor] = inst_uuid
1017       # double-check minor against reservation
1018       r_key = (nuuid, minor)
1019       assert r_key not in self._temporary_drbds, \
1020              ("Attempt to reuse reserved DRBD minor %d on node %s,"
1021               " reserved for instance %s" %
1022               (minor, nuuid, self._temporary_drbds[r_key]))
1023       self._temporary_drbds[r_key] = inst_uuid
1024       result.append(minor)
1025     logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1026                   node_uuids, result)
1027     return result
1028
1029   def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1030     """Release temporary drbd minors allocated for a given instance.
1031
1032     @type inst_uuid: string
1033     @param inst_uuid: the instance for which temporary minors should be
1034                       released
1035
1036     """
1037     assert isinstance(inst_uuid, basestring), \
1038            "Invalid argument passed to ReleaseDRBDMinors"
1039     for key, uuid in self._temporary_drbds.items():
1040       if uuid == inst_uuid:
1041         del self._temporary_drbds[key]
1042
1043   @locking.ssynchronized(_config_lock)
1044   def ReleaseDRBDMinors(self, inst_uuid):
1045     """Release temporary drbd minors allocated for a given instance.
1046
1047     This should be called on the error paths, on the success paths
1048     it's automatically called by the ConfigWriter add and update
1049     functions.
1050
1051     This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1052
1053     @type inst_uuid: string
1054     @param inst_uuid: the instance for which temporary minors should be
1055                       released
1056
1057     """
1058     self._UnlockedReleaseDRBDMinors(inst_uuid)
1059
1060   @locking.ssynchronized(_config_lock, shared=1)
1061   def GetConfigVersion(self):
1062     """Get the configuration version.
1063
1064     @return: Config version
1065
1066     """
1067     return self._config_data.version
1068
1069   @locking.ssynchronized(_config_lock, shared=1)
1070   def GetClusterName(self):
1071     """Get cluster name.
1072
1073     @return: Cluster name
1074
1075     """
1076     return self._config_data.cluster.cluster_name
1077
1078   @locking.ssynchronized(_config_lock, shared=1)
1079   def GetMasterNode(self):
1080     """Get the UUID of the master node for this cluster.
1081
1082     @return: Master node UUID
1083
1084     """
1085     return self._config_data.cluster.master_node
1086
1087   @locking.ssynchronized(_config_lock, shared=1)
1088   def GetMasterNodeName(self):
1089     """Get the hostname of the master node for this cluster.
1090
1091     @return: Master node hostname
1092
1093     """
1094     return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
1095
1096   @locking.ssynchronized(_config_lock, shared=1)
1097   def GetMasterNodeInfo(self):
1098     """Get the master node information for this cluster.
1099
1100     @rtype: objects.Node
1101     @return: Master node L{objects.Node} object
1102
1103     """
1104     return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
1105
1106   @locking.ssynchronized(_config_lock, shared=1)
1107   def GetMasterIP(self):
1108     """Get the IP of the master node for this cluster.
1109
1110     @return: Master IP
1111
1112     """
1113     return self._config_data.cluster.master_ip
1114
1115   @locking.ssynchronized(_config_lock, shared=1)
1116   def GetMasterNetdev(self):
1117     """Get the master network device for this cluster.
1118
1119     """
1120     return self._config_data.cluster.master_netdev
1121
1122   @locking.ssynchronized(_config_lock, shared=1)
1123   def GetMasterNetmask(self):
1124     """Get the netmask of the master node for this cluster.
1125
1126     """
1127     return self._config_data.cluster.master_netmask
1128
1129   @locking.ssynchronized(_config_lock, shared=1)
1130   def GetUseExternalMipScript(self):
1131     """Get flag representing whether to use the external master IP setup script.
1132
1133     """
1134     return self._config_data.cluster.use_external_mip_script
1135
1136   @locking.ssynchronized(_config_lock, shared=1)
1137   def GetFileStorageDir(self):
1138     """Get the file storage dir for this cluster.
1139
1140     """
1141     return self._config_data.cluster.file_storage_dir
1142
1143   @locking.ssynchronized(_config_lock, shared=1)
1144   def GetSharedFileStorageDir(self):
1145     """Get the shared file storage dir for this cluster.
1146
1147     """
1148     return self._config_data.cluster.shared_file_storage_dir
1149
1150   @locking.ssynchronized(_config_lock, shared=1)
1151   def GetHypervisorType(self):
1152     """Get the hypervisor type for this cluster.
1153
1154     """
1155     return self._config_data.cluster.enabled_hypervisors[0]
1156
1157   @locking.ssynchronized(_config_lock, shared=1)
1158   def GetRsaHostKey(self):
1159     """Return the rsa hostkey from the config.
1160
1161     @rtype: string
1162     @return: the rsa hostkey
1163
1164     """
1165     return self._config_data.cluster.rsahostkeypub
1166
1167   @locking.ssynchronized(_config_lock, shared=1)
1168   def GetDsaHostKey(self):
1169     """Return the dsa hostkey from the config.
1170
1171     @rtype: string
1172     @return: the dsa hostkey
1173
1174     """
1175     return self._config_data.cluster.dsahostkeypub
1176
1177   @locking.ssynchronized(_config_lock, shared=1)
1178   def GetDefaultIAllocator(self):
1179     """Get the default instance allocator for this cluster.
1180
1181     """
1182     return self._config_data.cluster.default_iallocator
1183
1184   @locking.ssynchronized(_config_lock, shared=1)
1185   def GetPrimaryIPFamily(self):
1186     """Get cluster primary ip family.
1187
1188     @return: primary ip family
1189
1190     """
1191     return self._config_data.cluster.primary_ip_family
1192
1193   @locking.ssynchronized(_config_lock, shared=1)
1194   def GetMasterNetworkParameters(self):
1195     """Get network parameters of the master node.
1196
1197     @rtype: L{object.MasterNetworkParameters}
1198     @return: network parameters of the master node
1199
1200     """
1201     cluster = self._config_data.cluster
1202     result = objects.MasterNetworkParameters(
1203       uuid=cluster.master_node, ip=cluster.master_ip,
1204       netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1205       ip_family=cluster.primary_ip_family)
1206
1207     return result
1208
1209   @locking.ssynchronized(_config_lock)
1210   def AddNodeGroup(self, group, ec_id, check_uuid=True):
1211     """Add a node group to the configuration.
1212
1213     This method calls group.UpgradeConfig() to fill any missing attributes
1214     according to their default values.
1215
1216     @type group: L{objects.NodeGroup}
1217     @param group: the NodeGroup object to add
1218     @type ec_id: string
1219     @param ec_id: unique id for the job to use when creating a missing UUID
1220     @type check_uuid: bool
1221     @param check_uuid: add an UUID to the group if it doesn't have one or, if
1222                        it does, ensure that it does not exist in the
1223                        configuration already
1224
1225     """
1226     self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1227     self._WriteConfig()
1228
1229   def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1230     """Add a node group to the configuration.
1231
1232     """
1233     logging.info("Adding node group %s to configuration", group.name)
1234
1235     # Some code might need to add a node group with a pre-populated UUID
1236     # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1237     # the "does this UUID" exist already check.
1238     if check_uuid:
1239       self._EnsureUUID(group, ec_id)
1240
1241     try:
1242       existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1243     except errors.OpPrereqError:
1244       pass
1245     else:
1246       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1247                                  " node group (UUID: %s)" %
1248                                  (group.name, existing_uuid),
1249                                  errors.ECODE_EXISTS)
1250
1251     group.serial_no = 1
1252     group.ctime = group.mtime = time.time()
1253     group.UpgradeConfig()
1254
1255     self._config_data.nodegroups[group.uuid] = group
1256     self._config_data.cluster.serial_no += 1
1257
1258   @locking.ssynchronized(_config_lock)
1259   def RemoveNodeGroup(self, group_uuid):
1260     """Remove a node group from the configuration.
1261
1262     @type group_uuid: string
1263     @param group_uuid: the UUID of the node group to remove
1264
1265     """
1266     logging.info("Removing node group %s from configuration", group_uuid)
1267
1268     if group_uuid not in self._config_data.nodegroups:
1269       raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1270
1271     assert len(self._config_data.nodegroups) != 1, \
1272             "Group '%s' is the only group, cannot be removed" % group_uuid
1273
1274     del self._config_data.nodegroups[group_uuid]
1275     self._config_data.cluster.serial_no += 1
1276     self._WriteConfig()
1277
1278   def _UnlockedLookupNodeGroup(self, target):
1279     """Lookup a node group's UUID.
1280
1281     @type target: string or None
1282     @param target: group name or UUID or None to look for the default
1283     @rtype: string
1284     @return: nodegroup UUID
1285     @raises errors.OpPrereqError: when the target group cannot be found
1286
1287     """
1288     if target is None:
1289       if len(self._config_data.nodegroups) != 1:
1290         raise errors.OpPrereqError("More than one node group exists. Target"
1291                                    " group must be specified explicitly.")
1292       else:
1293         return self._config_data.nodegroups.keys()[0]
1294     if target in self._config_data.nodegroups:
1295       return target
1296     for nodegroup in self._config_data.nodegroups.values():
1297       if nodegroup.name == target:
1298         return nodegroup.uuid
1299     raise errors.OpPrereqError("Node group '%s' not found" % target,
1300                                errors.ECODE_NOENT)
1301
1302   @locking.ssynchronized(_config_lock, shared=1)
1303   def LookupNodeGroup(self, target):
1304     """Lookup a node group's UUID.
1305
1306     This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1307
1308     @type target: string or None
1309     @param target: group name or UUID or None to look for the default
1310     @rtype: string
1311     @return: nodegroup UUID
1312
1313     """
1314     return self._UnlockedLookupNodeGroup(target)
1315
1316   def _UnlockedGetNodeGroup(self, uuid):
1317     """Lookup a node group.
1318
1319     @type uuid: string
1320     @param uuid: group UUID
1321     @rtype: L{objects.NodeGroup} or None
1322     @return: nodegroup object, or None if not found
1323
1324     """
1325     if uuid not in self._config_data.nodegroups:
1326       return None
1327
1328     return self._config_data.nodegroups[uuid]
1329
1330   @locking.ssynchronized(_config_lock, shared=1)
1331   def GetNodeGroup(self, uuid):
1332     """Lookup a node group.
1333
1334     @type uuid: string
1335     @param uuid: group UUID
1336     @rtype: L{objects.NodeGroup} or None
1337     @return: nodegroup object, or None if not found
1338
1339     """
1340     return self._UnlockedGetNodeGroup(uuid)
1341
1342   @locking.ssynchronized(_config_lock, shared=1)
1343   def GetAllNodeGroupsInfo(self):
1344     """Get the configuration of all node groups.
1345
1346     """
1347     return dict(self._config_data.nodegroups)
1348
1349   @locking.ssynchronized(_config_lock, shared=1)
1350   def GetNodeGroupList(self):
1351     """Get a list of node groups.
1352
1353     """
1354     return self._config_data.nodegroups.keys()
1355
1356   @locking.ssynchronized(_config_lock, shared=1)
1357   def GetNodeGroupMembersByNodes(self, nodes):
1358     """Get nodes which are member in the same nodegroups as the given nodes.
1359
1360     """
1361     ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1362     return frozenset(member_uuid
1363                      for node_uuid in nodes
1364                      for member_uuid in
1365                        self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1366
1367   @locking.ssynchronized(_config_lock, shared=1)
1368   def GetMultiNodeGroupInfo(self, group_uuids):
1369     """Get the configuration of multiple node groups.
1370
1371     @param group_uuids: List of node group UUIDs
1372     @rtype: list
1373     @return: List of tuples of (group_uuid, group_info)
1374
1375     """
1376     return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1377
1378   @locking.ssynchronized(_config_lock)
1379   def AddInstance(self, instance, ec_id):
1380     """Add an instance to the config.
1381
1382     This should be used after creating a new instance.
1383
1384     @type instance: L{objects.Instance}
1385     @param instance: the instance object
1386
1387     """
1388     if not isinstance(instance, objects.Instance):
1389       raise errors.ProgrammerError("Invalid type passed to AddInstance")
1390
1391     if instance.disk_template != constants.DT_DISKLESS:
1392       all_lvs = instance.MapLVsByNode()
1393       logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1394
1395     all_macs = self._AllMACs()
1396     for nic in instance.nics:
1397       if nic.mac in all_macs:
1398         raise errors.ConfigurationError("Cannot add instance %s:"
1399                                         " MAC address '%s' already in use." %
1400                                         (instance.name, nic.mac))
1401
1402     self._CheckUniqueUUID(instance, include_temporary=False)
1403
1404     instance.serial_no = 1
1405     instance.ctime = instance.mtime = time.time()
1406     self._config_data.instances[instance.uuid] = instance
1407     self._config_data.cluster.serial_no += 1
1408     self._UnlockedReleaseDRBDMinors(instance.uuid)
1409     self._UnlockedCommitTemporaryIps(ec_id)
1410     self._WriteConfig()
1411
1412   def _EnsureUUID(self, item, ec_id):
1413     """Ensures a given object has a valid UUID.
1414
1415     @param item: the instance or node to be checked
1416     @param ec_id: the execution context id for the uuid reservation
1417
1418     """
1419     if not item.uuid:
1420       item.uuid = self._GenerateUniqueID(ec_id)
1421     else:
1422       self._CheckUniqueUUID(item, include_temporary=True)
1423
1424   def _CheckUniqueUUID(self, item, include_temporary):
1425     """Checks that the UUID of the given object is unique.
1426
1427     @param item: the instance or node to be checked
1428     @param include_temporary: whether temporarily generated UUID's should be
1429               included in the check. If the UUID of the item to be checked is
1430               a temporarily generated one, this has to be C{False}.
1431
1432     """
1433     if not item.uuid:
1434       raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1435     if item.uuid in self._AllIDs(include_temporary=include_temporary):
1436       raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1437                                       " in use" % (item.name, item.uuid))
1438
1439   def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1440     """Set the instance's status to a given value.
1441
1442     """
1443     if inst_uuid not in self._config_data.instances:
1444       raise errors.ConfigurationError("Unknown instance '%s'" %
1445                                       inst_uuid)
1446     instance = self._config_data.instances[inst_uuid]
1447
1448     if status is None:
1449       status = instance.admin_state
1450     if disks_active is None:
1451       disks_active = instance.disks_active
1452
1453     assert status in constants.ADMINST_ALL, \
1454            "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1455
1456     if instance.admin_state != status or \
1457        instance.disks_active != disks_active:
1458       instance.admin_state = status
1459       instance.disks_active = disks_active
1460       instance.serial_no += 1
1461       instance.mtime = time.time()
1462       self._WriteConfig()
1463
1464   @locking.ssynchronized(_config_lock)
1465   def MarkInstanceUp(self, inst_uuid):
1466     """Mark the instance status to up in the config.
1467
1468     This also sets the instance disks active flag.
1469
1470     """
1471     self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1472
1473   @locking.ssynchronized(_config_lock)
1474   def MarkInstanceOffline(self, inst_uuid):
1475     """Mark the instance status to down in the config.
1476
1477     This also clears the instance disks active flag.
1478
1479     """
1480     self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1481
1482   @locking.ssynchronized(_config_lock)
1483   def RemoveInstance(self, inst_uuid):
1484     """Remove the instance from the configuration.
1485
1486     """
1487     if inst_uuid not in self._config_data.instances:
1488       raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1489
1490     # If a network port has been allocated to the instance,
1491     # return it to the pool of free ports.
1492     inst = self._config_data.instances[inst_uuid]
1493     network_port = getattr(inst, "network_port", None)
1494     if network_port is not None:
1495       self._config_data.cluster.tcpudp_port_pool.add(network_port)
1496
1497     instance = self._UnlockedGetInstanceInfo(inst_uuid)
1498
1499     for nic in instance.nics:
1500       if nic.network and nic.ip:
1501         # Return all IP addresses to the respective address pools
1502         self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1503
1504     del self._config_data.instances[inst_uuid]
1505     self._config_data.cluster.serial_no += 1
1506     self._WriteConfig()
1507
1508   @locking.ssynchronized(_config_lock)
1509   def RenameInstance(self, inst_uuid, new_name):
1510     """Rename an instance.
1511
1512     This needs to be done in ConfigWriter and not by RemoveInstance
1513     combined with AddInstance as only we can guarantee an atomic
1514     rename.
1515
1516     """
1517     if inst_uuid not in self._config_data.instances:
1518       raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1519
1520     inst = self._config_data.instances[inst_uuid]
1521     inst.name = new_name
1522
1523     for (idx, disk) in enumerate(inst.disks):
1524       if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1525         # rename the file paths in logical and physical id
1526         file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1527         disk.logical_id = (disk.logical_id[0],
1528                            utils.PathJoin(file_storage_dir, inst.name,
1529                                           "disk%s" % idx))
1530
1531     # Force update of ssconf files
1532     self._config_data.cluster.serial_no += 1
1533
1534     self._WriteConfig()
1535
1536   @locking.ssynchronized(_config_lock)
1537   def MarkInstanceDown(self, inst_uuid):
1538     """Mark the status of an instance to down in the configuration.
1539
1540     This does not touch the instance disks active flag, as shut down instances
1541     can still have active disks.
1542
1543     """
1544     self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1545
1546   @locking.ssynchronized(_config_lock)
1547   def MarkInstanceDisksActive(self, inst_uuid):
1548     """Mark the status of instance disks active.
1549
1550     """
1551     self._SetInstanceStatus(inst_uuid, None, True)
1552
1553   @locking.ssynchronized(_config_lock)
1554   def MarkInstanceDisksInactive(self, inst_uuid):
1555     """Mark the status of instance disks inactive.
1556
1557     """
1558     self._SetInstanceStatus(inst_uuid, None, False)
1559
1560   def _UnlockedGetInstanceList(self):
1561     """Get the list of instances.
1562
1563     This function is for internal use, when the config lock is already held.
1564
1565     """
1566     return self._config_data.instances.keys()
1567
1568   @locking.ssynchronized(_config_lock, shared=1)
1569   def GetInstanceList(self):
1570     """Get the list of instances.
1571
1572     @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid']
1573
1574     """
1575     return self._UnlockedGetInstanceList()
1576
1577   def ExpandInstanceName(self, short_name):
1578     """Attempt to expand an incomplete instance name.
1579
1580     """
1581     # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1582     all_insts = self.GetAllInstancesInfo().values()
1583     expanded_name = _MatchNameComponentIgnoreCase(
1584                       short_name, [inst.name for inst in all_insts])
1585
1586     if expanded_name is not None:
1587       # there has to be exactly one instance with that name
1588       inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1589       return (inst.uuid, inst.name)
1590     else:
1591       return (None, None)
1592
1593   def _UnlockedGetInstanceInfo(self, inst_uuid):
1594     """Returns information about an instance.
1595
1596     This function is for internal use, when the config lock is already held.
1597
1598     """
1599     if inst_uuid not in self._config_data.instances:
1600       return None
1601
1602     return self._config_data.instances[inst_uuid]
1603
1604   @locking.ssynchronized(_config_lock, shared=1)
1605   def GetInstanceInfo(self, inst_uuid):
1606     """Returns information about an instance.
1607
1608     It takes the information from the configuration file. Other information of
1609     an instance are taken from the live systems.
1610
1611     @param inst_uuid: UUID of the instance
1612
1613     @rtype: L{objects.Instance}
1614     @return: the instance object
1615
1616     """
1617     return self._UnlockedGetInstanceInfo(inst_uuid)
1618
1619   @locking.ssynchronized(_config_lock, shared=1)
1620   def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1621     """Returns set of node group UUIDs for instance's nodes.
1622
1623     @rtype: frozenset
1624
1625     """
1626     instance = self._UnlockedGetInstanceInfo(inst_uuid)
1627     if not instance:
1628       raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1629
1630     if primary_only:
1631       nodes = [instance.primary_node]
1632     else:
1633       nodes = instance.all_nodes
1634
1635     return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1636                      for node_uuid in nodes)
1637
1638   @locking.ssynchronized(_config_lock, shared=1)
1639   def GetInstanceNetworks(self, inst_uuid):
1640     """Returns set of network UUIDs for instance's nics.
1641
1642     @rtype: frozenset
1643
1644     """
1645     instance = self._UnlockedGetInstanceInfo(inst_uuid)
1646     if not instance:
1647       raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1648
1649     networks = set()
1650     for nic in instance.nics:
1651       if nic.network:
1652         networks.add(nic.network)
1653
1654     return frozenset(networks)
1655
1656   @locking.ssynchronized(_config_lock, shared=1)
1657   def GetMultiInstanceInfo(self, inst_uuids):
1658     """Get the configuration of multiple instances.
1659
1660     @param inst_uuids: list of instance UUIDs
1661     @rtype: list
1662     @return: list of tuples (instance UUID, instance_info), where
1663         instance_info is what would GetInstanceInfo return for the
1664         node, while keeping the original order
1665
1666     """
1667     return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1668
1669   @locking.ssynchronized(_config_lock, shared=1)
1670   def GetMultiInstanceInfoByName(self, inst_names):
1671     """Get the configuration of multiple instances.
1672
1673     @param inst_names: list of instance names
1674     @rtype: list
1675     @return: list of tuples (instance, instance_info), where
1676         instance_info is what would GetInstanceInfo return for the
1677         node, while keeping the original order
1678
1679     """
1680     result = []
1681     for name in inst_names:
1682       instance = self._UnlockedGetInstanceInfoByName(name)
1683       result.append((instance.uuid, instance))
1684     return result
1685
1686   @locking.ssynchronized(_config_lock, shared=1)
1687   def GetAllInstancesInfo(self):
1688     """Get the configuration of all instances.
1689
1690     @rtype: dict
1691     @return: dict of (instance, instance_info), where instance_info is what
1692               would GetInstanceInfo return for the node
1693
1694     """
1695     return self._UnlockedGetAllInstancesInfo()
1696
1697   def _UnlockedGetAllInstancesInfo(self):
1698     my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1699                     for inst_uuid in self._UnlockedGetInstanceList()])
1700     return my_dict
1701
1702   @locking.ssynchronized(_config_lock, shared=1)
1703   def GetInstancesInfoByFilter(self, filter_fn):
1704     """Get instance configuration with a filter.
1705
1706     @type filter_fn: callable
1707     @param filter_fn: Filter function receiving instance object as parameter,
1708       returning boolean. Important: this function is called while the
1709       configuration locks is held. It must not do any complex work or call
1710       functions potentially leading to a deadlock. Ideally it doesn't call any
1711       other functions and just compares instance attributes.
1712
1713     """
1714     return dict((uuid, inst)
1715                 for (uuid, inst) in self._config_data.instances.items()
1716                 if filter_fn(inst))
1717
1718   @locking.ssynchronized(_config_lock, shared=1)
1719   def GetInstanceInfoByName(self, inst_name):
1720     """Get the L{objects.Instance} object for a named instance.
1721
1722     @param inst_name: name of the instance to get information for
1723     @type inst_name: string
1724     @return: the corresponding L{objects.Instance} instance or None if no
1725           information is available
1726
1727     """
1728     return self._UnlockedGetInstanceInfoByName(inst_name)
1729
1730   def _UnlockedGetInstanceInfoByName(self, inst_name):
1731     for inst in self._UnlockedGetAllInstancesInfo().values():
1732       if inst.name == inst_name:
1733         return inst
1734     return None
1735
1736   def _UnlockedGetInstanceName(self, inst_uuid):
1737     inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1738     if inst_info is None:
1739       raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1740     return inst_info.name
1741
1742   @locking.ssynchronized(_config_lock, shared=1)
1743   def GetInstanceName(self, inst_uuid):
1744     """Gets the instance name for the passed instance.
1745
1746     @param inst_uuid: instance UUID to get name for
1747     @type inst_uuid: string
1748     @rtype: string
1749     @return: instance name
1750
1751     """
1752     return self._UnlockedGetInstanceName(inst_uuid)
1753
1754   @locking.ssynchronized(_config_lock, shared=1)
1755   def GetInstanceNames(self, inst_uuids):
1756     """Gets the instance names for the passed list of nodes.
1757
1758     @param inst_uuids: list of instance UUIDs to get names for
1759     @type inst_uuids: list of strings
1760     @rtype: list of strings
1761     @return: list of instance names
1762
1763     """
1764     return self._UnlockedGetInstanceNames(inst_uuids)
1765
1766   def _UnlockedGetInstanceNames(self, inst_uuids):
1767     return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1768
1769   @locking.ssynchronized(_config_lock)
1770   def AddNode(self, node, ec_id):
1771     """Add a node to the configuration.
1772
1773     @type node: L{objects.Node}
1774     @param node: a Node instance
1775
1776     """
1777     logging.info("Adding node %s to configuration", node.name)
1778
1779     self._EnsureUUID(node, ec_id)
1780
1781     node.serial_no = 1
1782     node.ctime = node.mtime = time.time()
1783     self._UnlockedAddNodeToGroup(node.uuid, node.group)
1784     self._config_data.nodes[node.uuid] = node
1785     self._config_data.cluster.serial_no += 1
1786     self._WriteConfig()
1787
1788   @locking.ssynchronized(_config_lock)
1789   def RemoveNode(self, node_uuid):
1790     """Remove a node from the configuration.
1791
1792     """
1793     logging.info("Removing node %s from configuration", node_uuid)
1794
1795     if node_uuid not in self._config_data.nodes:
1796       raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1797
1798     self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1799     del self._config_data.nodes[node_uuid]
1800     self._config_data.cluster.serial_no += 1
1801     self._WriteConfig()
1802
1803   def ExpandNodeName(self, short_name):
1804     """Attempt to expand an incomplete node name into a node UUID.
1805
1806     """
1807     # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1808     all_nodes = self.GetAllNodesInfo().values()
1809     expanded_name = _MatchNameComponentIgnoreCase(
1810                       short_name, [node.name for node in all_nodes])
1811
1812     if expanded_name is not None:
1813       # there has to be exactly one node with that name
1814       node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1815       return (node.uuid, node.name)
1816     else:
1817       return (None, None)
1818
1819   def _UnlockedGetNodeInfo(self, node_uuid):
1820     """Get the configuration of a node, as stored in the config.
1821
1822     This function is for internal use, when the config lock is already
1823     held.
1824
1825     @param node_uuid: the node UUID
1826
1827     @rtype: L{objects.Node}
1828     @return: the node object
1829
1830     """
1831     if node_uuid not in self._config_data.nodes:
1832       return None
1833
1834     return self._config_data.nodes[node_uuid]
1835
1836   @locking.ssynchronized(_config_lock, shared=1)
1837   def GetNodeInfo(self, node_uuid):
1838     """Get the configuration of a node, as stored in the config.
1839
1840     This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1841
1842     @param node_uuid: the node UUID
1843
1844     @rtype: L{objects.Node}
1845     @return: the node object
1846
1847     """
1848     return self._UnlockedGetNodeInfo(node_uuid)
1849
1850   @locking.ssynchronized(_config_lock, shared=1)
1851   def GetNodeInstances(self, node_uuid):
1852     """Get the instances of a node, as stored in the config.
1853
1854     @param node_uuid: the node UUID
1855
1856     @rtype: (list, list)
1857     @return: a tuple with two lists: the primary and the secondary instances
1858
1859     """
1860     pri = []
1861     sec = []
1862     for inst in self._config_data.instances.values():
1863       if inst.primary_node == node_uuid:
1864         pri.append(inst.uuid)
1865       if node_uuid in inst.secondary_nodes:
1866         sec.append(inst.uuid)
1867     return (pri, sec)
1868
1869   @locking.ssynchronized(_config_lock, shared=1)
1870   def GetNodeGroupInstances(self, uuid, primary_only=False):
1871     """Get the instances of a node group.
1872
1873     @param uuid: Node group UUID
1874     @param primary_only: Whether to only consider primary nodes
1875     @rtype: frozenset
1876     @return: List of instance UUIDs in node group
1877
1878     """
1879     if primary_only:
1880       nodes_fn = lambda inst: [inst.primary_node]
1881     else:
1882       nodes_fn = lambda inst: inst.all_nodes
1883
1884     return frozenset(inst.uuid
1885                      for inst in self._config_data.instances.values()
1886                      for node_uuid in nodes_fn(inst)
1887                      if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1888
1889   def _UnlockedGetHvparamsString(self, hvname):
1890     """Return the string representation of the list of hyervisor parameters of
1891     the given hypervisor.
1892
1893     @see: C{GetHvparams}
1894
1895     """
1896     result = ""
1897     hvparams = self._config_data.cluster.hvparams[hvname]
1898     for key in hvparams:
1899       result += "%s=%s\n" % (key, hvparams[key])
1900     return result
1901
1902   @locking.ssynchronized(_config_lock, shared=1)
1903   def GetHvparamsString(self, hvname):
1904     """Return the hypervisor parameters of the given hypervisor.
1905
1906     @type hvname: string
1907     @param hvname: name of a hypervisor
1908     @rtype: string
1909     @return: string containing key-value-pairs, one pair on each line;
1910       format: KEY=VALUE
1911
1912     """
1913     return self._UnlockedGetHvparamsString(hvname)
1914
1915   def _UnlockedGetNodeList(self):
1916     """Return the list of nodes which are in the configuration.
1917
1918     This function is for internal use, when the config lock is already
1919     held.
1920
1921     @rtype: list
1922
1923     """
1924     return self._config_data.nodes.keys()
1925
1926   @locking.ssynchronized(_config_lock, shared=1)
1927   def GetNodeList(self):
1928     """Return the list of nodes which are in the configuration.
1929
1930     """
1931     return self._UnlockedGetNodeList()
1932
1933   def _UnlockedGetOnlineNodeList(self):
1934     """Return the list of nodes which are online.
1935
1936     """
1937     all_nodes = [self._UnlockedGetNodeInfo(node)
1938                  for node in self._UnlockedGetNodeList()]
1939     return [node.uuid for node in all_nodes if not node.offline]
1940
1941   @locking.ssynchronized(_config_lock, shared=1)
1942   def GetOnlineNodeList(self):
1943     """Return the list of nodes which are online.
1944
1945     """
1946     return self._UnlockedGetOnlineNodeList()
1947
1948   @locking.ssynchronized(_config_lock, shared=1)
1949   def GetVmCapableNodeList(self):
1950     """Return the list of nodes which are not vm capable.
1951
1952     """
1953     all_nodes = [self._UnlockedGetNodeInfo(node)
1954                  for node in self._UnlockedGetNodeList()]
1955     return [node.uuid for node in all_nodes if node.vm_capable]
1956
1957   @locking.ssynchronized(_config_lock, shared=1)
1958   def GetNonVmCapableNodeList(self):
1959     """Return the list of nodes which are not vm capable.
1960
1961     """
1962     all_nodes = [self._UnlockedGetNodeInfo(node)
1963                  for node in self._UnlockedGetNodeList()]
1964     return [node.uuid for node in all_nodes if not node.vm_capable]
1965
1966   @locking.ssynchronized(_config_lock, shared=1)
1967   def GetMultiNodeInfo(self, node_uuids):
1968     """Get the configuration of multiple nodes.
1969
1970     @param node_uuids: list of node UUIDs
1971     @rtype: list
1972     @return: list of tuples of (node, node_info), where node_info is
1973         what would GetNodeInfo return for the node, in the original
1974         order
1975
1976     """
1977     return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
1978
1979   def _UnlockedGetAllNodesInfo(self):
1980     """Gets configuration of all nodes.
1981
1982     @note: See L{GetAllNodesInfo}
1983
1984     """
1985     return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
1986                  for node_uuid in self._UnlockedGetNodeList()])
1987
1988   @locking.ssynchronized(_config_lock, shared=1)
1989   def GetAllNodesInfo(self):
1990     """Get the configuration of all nodes.
1991
1992     @rtype: dict
1993     @return: dict of (node, node_info), where node_info is what
1994               would GetNodeInfo return for the node
1995
1996     """
1997     return self._UnlockedGetAllNodesInfo()
1998
1999   def _UnlockedGetNodeInfoByName(self, node_name):
2000     for node in self._UnlockedGetAllNodesInfo().values():
2001       if node.name == node_name:
2002         return node
2003     return None
2004
2005   @locking.ssynchronized(_config_lock, shared=1)
2006   def GetNodeInfoByName(self, node_name):
2007     """Get the L{objects.Node} object for a named node.
2008
2009     @param node_name: name of the node to get information for
2010     @type node_name: string
2011     @return: the corresponding L{objects.Node} instance or None if no
2012           information is available
2013
2014     """
2015     return self._UnlockedGetNodeInfoByName(node_name)
2016
2017   def _UnlockedGetNodeName(self, node_spec):
2018     if isinstance(node_spec, objects.Node):
2019       return node_spec.name
2020     elif isinstance(node_spec, basestring):
2021       node_info = self._UnlockedGetNodeInfo(node_spec)
2022       if node_info is None:
2023         raise errors.OpExecError("Unknown node: %s" % node_spec)
2024       return node_info.name
2025     else:
2026       raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2027
2028   @locking.ssynchronized(_config_lock, shared=1)
2029   def GetNodeName(self, node_spec):
2030     """Gets the node name for the passed node.
2031
2032     @param node_spec: node to get names for
2033     @type node_spec: either node UUID or a L{objects.Node} object
2034     @rtype: string
2035     @return: node name
2036
2037     """
2038     return self._UnlockedGetNodeName(node_spec)
2039
2040   def _UnlockedGetNodeNames(self, node_specs):
2041     return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2042
2043   @locking.ssynchronized(_config_lock, shared=1)
2044   def GetNodeNames(self, node_specs):
2045     """Gets the node names for the passed list of nodes.
2046
2047     @param node_specs: list of nodes to get names for
2048     @type node_specs: list of either node UUIDs or L{objects.Node} objects
2049     @rtype: list of strings
2050     @return: list of node names
2051
2052     """
2053     return self._UnlockedGetNodeNames(node_specs)
2054
2055   @locking.ssynchronized(_config_lock, shared=1)
2056   def GetNodeGroupsFromNodes(self, node_uuids):
2057     """Returns groups for a list of nodes.
2058
2059     @type node_uuids: list of string
2060     @param node_uuids: List of node UUIDs
2061     @rtype: frozenset
2062
2063     """
2064     return frozenset(self._UnlockedGetNodeInfo(uuid).group
2065                      for uuid in node_uuids)
2066
2067   def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2068     """Get the number of current and maximum desired and possible candidates.
2069
2070     @type exceptions: list
2071     @param exceptions: if passed, list of nodes that should be ignored
2072     @rtype: tuple
2073     @return: tuple of (current, desired and possible, possible)
2074
2075     """
2076     mc_now = mc_should = mc_max = 0
2077     for node in self._config_data.nodes.values():
2078       if exceptions and node.uuid in exceptions:
2079         continue
2080       if not (node.offline or node.drained) and node.master_capable:
2081         mc_max += 1
2082       if node.master_candidate:
2083         mc_now += 1
2084     mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2085     return (mc_now, mc_should, mc_max)
2086
2087   @locking.ssynchronized(_config_lock, shared=1)
2088   def GetMasterCandidateStats(self, exceptions=None):
2089     """Get the number of current and maximum possible candidates.
2090
2091     This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2092
2093     @type exceptions: list
2094     @param exceptions: if passed, list of nodes that should be ignored
2095     @rtype: tuple
2096     @return: tuple of (current, max)
2097
2098     """
2099     return self._UnlockedGetMasterCandidateStats(exceptions)
2100
2101   @locking.ssynchronized(_config_lock)
2102   def MaintainCandidatePool(self, exception_node_uuids):
2103     """Try to grow the candidate pool to the desired size.
2104
2105     @type exception_node_uuids: list
2106     @param exception_node_uuids: if passed, list of nodes that should be ignored
2107     @rtype: list
2108     @return: list with the adjusted nodes (L{objects.Node} instances)
2109
2110     """
2111     mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2112                           exception_node_uuids)
2113     mod_list = []
2114     if mc_now < mc_max:
2115       node_list = self._config_data.nodes.keys()
2116       random.shuffle(node_list)
2117       for uuid in node_list:
2118         if mc_now >= mc_max:
2119           break
2120         node = self._config_data.nodes[uuid]
2121         if (node.master_candidate or node.offline or node.drained or
2122             node.uuid in exception_node_uuids or not node.master_capable):
2123           continue
2124         mod_list.append(node)
2125         node.master_candidate = True
2126         node.serial_no += 1
2127         mc_now += 1
2128       if mc_now != mc_max:
2129         # this should not happen
2130         logging.warning("Warning: MaintainCandidatePool didn't manage to"
2131                         " fill the candidate pool (%d/%d)", mc_now, mc_max)
2132       if mod_list:
2133         self._config_data.cluster.serial_no += 1
2134         self._WriteConfig()
2135
2136     return mod_list
2137
2138   def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2139     """Add a given node to the specified group.
2140
2141     """
2142     if nodegroup_uuid not in self._config_data.nodegroups:
2143       # This can happen if a node group gets deleted between its lookup and
2144       # when we're adding the first node to it, since we don't keep a lock in
2145       # the meantime. It's ok though, as we'll fail cleanly if the node group
2146       # is not found anymore.
2147       raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2148     if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2149       self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2150
2151   def _UnlockedRemoveNodeFromGroup(self, node):
2152     """Remove a given node from its group.
2153
2154     """
2155     nodegroup = node.group
2156     if nodegroup not in self._config_data.nodegroups:
2157       logging.warning("Warning: node '%s' has unknown node group '%s'"
2158                       " (while being removed from it)", node.uuid, nodegroup)
2159     nodegroup_obj = self._config_data.nodegroups[nodegroup]
2160     if node.uuid not in nodegroup_obj.members:
2161       logging.warning("Warning: node '%s' not a member of its node group '%s'"
2162                       " (while being removed from it)", node.uuid, nodegroup)
2163     else:
2164       nodegroup_obj.members.remove(node.uuid)
2165
2166   @locking.ssynchronized(_config_lock)
2167   def AssignGroupNodes(self, mods):
2168     """Changes the group of a number of nodes.
2169
2170     @type mods: list of tuples; (node name, new group UUID)
2171     @param mods: Node membership modifications
2172
2173     """
2174     groups = self._config_data.nodegroups
2175     nodes = self._config_data.nodes
2176
2177     resmod = []
2178
2179     # Try to resolve UUIDs first
2180     for (node_uuid, new_group_uuid) in mods:
2181       try:
2182         node = nodes[node_uuid]
2183       except KeyError:
2184         raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2185
2186       if node.group == new_group_uuid:
2187         # Node is being assigned to its current group
2188         logging.debug("Node '%s' was assigned to its current group (%s)",
2189                       node_uuid, node.group)
2190         continue
2191
2192       # Try to find current group of node
2193       try:
2194         old_group = groups[node.group]
2195       except KeyError:
2196         raise errors.ConfigurationError("Unable to find old group '%s'" %
2197                                         node.group)
2198
2199       # Try to find new group for node
2200       try:
2201         new_group = groups[new_group_uuid]
2202       except KeyError:
2203         raise errors.ConfigurationError("Unable to find new group '%s'" %
2204                                         new_group_uuid)
2205
2206       assert node.uuid in old_group.members, \
2207         ("Inconsistent configuration: node '%s' not listed in members for its"
2208          " old group '%s'" % (node.uuid, old_group.uuid))
2209       assert node.uuid not in new_group.members, \
2210         ("Inconsistent configuration: node '%s' already listed in members for"
2211          " its new group '%s'" % (node.uuid, new_group.uuid))
2212
2213       resmod.append((node, old_group, new_group))
2214
2215     # Apply changes
2216     for (node, old_group, new_group) in resmod:
2217       assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2218         "Assigning to current group is not possible"
2219
2220       node.group = new_group.uuid
2221
2222       # Update members of involved groups
2223       if node.uuid in old_group.members:
2224         old_group.members.remove(node.uuid)
2225       if node.uuid not in new_group.members:
2226         new_group.members.append(node.uuid)
2227
2228     # Update timestamps and serials (only once per node/group object)
2229     now = time.time()
2230     for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2231       obj.serial_no += 1
2232       obj.mtime = now
2233
2234     # Force ssconf update
2235     self._config_data.cluster.serial_no += 1
2236
2237     self._WriteConfig()
2238
2239   def _BumpSerialNo(self):
2240     """Bump up the serial number of the config.
2241
2242     """
2243     self._config_data.serial_no += 1
2244     self._config_data.mtime = time.time()
2245
2246   def _AllUUIDObjects(self):
2247     """Returns all objects with uuid attributes.
2248
2249     """
2250     return (self._config_data.instances.values() +
2251             self._config_data.nodes.values() +
2252             self._config_data.nodegroups.values() +
2253             self._config_data.networks.values() +
2254             self._AllDisks() +
2255             self._AllNICs() +
2256             [self._config_data.cluster])
2257
2258   def _OpenConfig(self, accept_foreign):
2259     """Read the config data from disk.
2260
2261     """
2262     raw_data = utils.ReadFile(self._cfg_file)
2263
2264     try:
2265       data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2266     except Exception, err:
2267       raise errors.ConfigurationError(err)
2268
2269     # Make sure the configuration has the right version
2270     _ValidateConfig(data)
2271
2272     if (not hasattr(data, "cluster") or
2273         not hasattr(data.cluster, "rsahostkeypub")):
2274       raise errors.ConfigurationError("Incomplete configuration"
2275                                       " (missing cluster.rsahostkeypub)")
2276
2277     if not data.cluster.master_node in data.nodes:
2278       msg = ("The configuration denotes node %s as master, but does not"
2279              " contain information about this node" %
2280              data.cluster.master_node)
2281       raise errors.ConfigurationError(msg)
2282
2283     master_info = data.nodes[data.cluster.master_node]
2284     if master_info.name != self._my_hostname and not accept_foreign:
2285       msg = ("The configuration denotes node %s as master, while my"
2286              " hostname is %s; opening a foreign configuration is only"
2287              " possible in accept_foreign mode" %
2288              (master_info.name, self._my_hostname))
2289       raise errors.ConfigurationError(msg)
2290
2291     self._config_data = data
2292     # reset the last serial as -1 so that the next write will cause
2293     # ssconf update
2294     self._last_cluster_serial = -1
2295
2296     # Upgrade configuration if needed
2297     self._UpgradeConfig()
2298
2299     self._cfg_id = utils.GetFileID(path=self._cfg_file)
2300
2301   def _UpgradeConfig(self):
2302     """Run any upgrade steps.
2303
2304     This method performs both in-object upgrades and also update some data
2305     elements that need uniqueness across the whole configuration or interact
2306     with other objects.
2307
2308     @warning: this function will call L{_WriteConfig()}, but also
2309         L{DropECReservations} so it needs to be called only from a
2310         "safe" place (the constructor). If one wanted to call it with
2311         the lock held, a DropECReservationUnlocked would need to be
2312         created first, to avoid causing deadlock.
2313
2314     """
2315     # Keep a copy of the persistent part of _config_data to check for changes
2316     # Serialization doesn't guarantee order in dictionaries
2317     oldconf = copy.deepcopy(self._config_data.ToDict())
2318
2319     # In-object upgrades
2320     self._config_data.UpgradeConfig()
2321
2322     for item in self._AllUUIDObjects():
2323       if item.uuid is None:
2324         item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2325     if not self._config_data.nodegroups:
2326       default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2327       default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2328                                             members=[])
2329       self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2330     for node in self._config_data.nodes.values():
2331       if not node.group:
2332         node.group = self.LookupNodeGroup(None)
2333       # This is technically *not* an upgrade, but needs to be done both when
2334       # nodegroups are being added, and upon normally loading the config,
2335       # because the members list of a node group is discarded upon
2336       # serializing/deserializing the object.
2337       self._UnlockedAddNodeToGroup(node.uuid, node.group)
2338
2339     modified = (oldconf != self._config_data.ToDict())
2340     if modified:
2341       self._WriteConfig()
2342       # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2343       # only called at config init time, without the lock held
2344       self.DropECReservations(_UPGRADE_CONFIG_JID)
2345     else:
2346       config_errors = self._UnlockedVerifyConfig()
2347       if config_errors:
2348         errmsg = ("Loaded configuration data is not consistent: %s" %
2349                   (utils.CommaJoin(config_errors)))
2350         logging.critical(errmsg)
2351
2352   def _DistributeConfig(self, feedback_fn):
2353     """Distribute the configuration to the other nodes.
2354
2355     Currently, this only copies the configuration file. In the future,
2356     it could be used to encapsulate the 2/3-phase update mechanism.
2357
2358     """
2359     if self._offline:
2360       return True
2361
2362     bad = False
2363
2364     node_list = []
2365     addr_list = []
2366     myhostname = self._my_hostname
2367     # we can skip checking whether _UnlockedGetNodeInfo returns None
2368     # since the node list comes from _UnlocketGetNodeList, and we are
2369     # called with the lock held, so no modifications should take place
2370     # in between
2371     for node_uuid in self._UnlockedGetNodeList():
2372       node_info = self._UnlockedGetNodeInfo(node_uuid)
2373       if node_info.name == myhostname or not node_info.master_candidate:
2374         continue
2375       node_list.append(node_info.name)
2376       addr_list.append(node_info.primary_ip)
2377
2378     # TODO: Use dedicated resolver talking to config writer for name resolution
2379     result = \
2380       self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2381     for to_node, to_result in result.items():
2382       msg = to_result.fail_msg
2383       if msg:
2384         msg = ("Copy of file %s to node %s failed: %s" %
2385                (self._cfg_file, to_node, msg))
2386         logging.error(msg)
2387
2388         if feedback_fn:
2389           feedback_fn(msg)
2390
2391         bad = True
2392
2393     return not bad
2394
2395   def _WriteConfig(self, destination=None, feedback_fn=None):
2396     """Write the configuration data to persistent storage.
2397
2398     """
2399     assert feedback_fn is None or callable(feedback_fn)
2400
2401     # Warn on config errors, but don't abort the save - the
2402     # configuration has already been modified, and we can't revert;
2403     # the best we can do is to warn the user and save as is, leaving
2404     # recovery to the user
2405     config_errors = self._UnlockedVerifyConfig()
2406     if config_errors:
2407       errmsg = ("Configuration data is not consistent: %s" %
2408                 (utils.CommaJoin(config_errors)))
2409       logging.critical(errmsg)
2410       if feedback_fn:
2411         feedback_fn(errmsg)
2412
2413     if destination is None:
2414       destination = self._cfg_file
2415     self._BumpSerialNo()
2416     txt = serializer.Dump(self._config_data.ToDict())
2417
2418     getents = self._getents()
2419     try:
2420       fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2421                                close=False, gid=getents.confd_gid, mode=0640)
2422     except errors.LockError:
2423       raise errors.ConfigurationError("The configuration file has been"
2424                                       " modified since the last write, cannot"
2425                                       " update")
2426     try:
2427       self._cfg_id = utils.GetFileID(fd=fd)
2428     finally:
2429       os.close(fd)
2430
2431     self.write_count += 1
2432
2433     # and redistribute the config file to master candidates
2434     self._DistributeConfig(feedback_fn)
2435
2436     # Write ssconf files on all nodes (including locally)
2437     if self._last_cluster_serial < self._config_data.cluster.serial_no:
2438       if not self._offline:
2439         result = self._GetRpc(None).call_write_ssconf_files(
2440           self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2441           self._UnlockedGetSsconfValues())
2442
2443         for nname, nresu in result.items():
2444           msg = nresu.fail_msg
2445           if msg:
2446             errmsg = ("Error while uploading ssconf files to"
2447                       " node %s: %s" % (nname, msg))
2448             logging.warning(errmsg)
2449
2450             if feedback_fn:
2451               feedback_fn(errmsg)
2452
2453       self._last_cluster_serial = self._config_data.cluster.serial_no
2454
2455   def _GetAllHvparamsStrings(self, hypervisors):
2456     """Get the hvparams of all given hypervisors from the config.
2457
2458     @type hypervisors: list of string
2459     @param hypervisors: list of hypervisor names
2460     @rtype: dict of strings
2461     @returns: dictionary mapping the hypervisor name to a string representation
2462       of the hypervisor's hvparams
2463
2464     """
2465     hvparams = {}
2466     for hv in hypervisors:
2467       hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2468     return hvparams
2469
2470   @staticmethod
2471   def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2472     """Extends the ssconf_values dictionary by hvparams.
2473
2474     @type ssconf_values: dict of strings
2475     @param ssconf_values: dictionary mapping ssconf_keys to strings
2476       representing the content of ssconf files
2477     @type all_hvparams: dict of strings
2478     @param all_hvparams: dictionary mapping hypervisor names to a string
2479       representation of their hvparams
2480     @rtype: same as ssconf_values
2481     @returns: the ssconf_values dictionary extended by hvparams
2482
2483     """
2484     for hv in all_hvparams:
2485       ssconf_key = constants.SS_HVPARAMS_PREF + hv
2486       ssconf_values[ssconf_key] = all_hvparams[hv]
2487     return ssconf_values
2488
2489   def _UnlockedGetSsconfValues(self):
2490     """Return the values needed by ssconf.
2491
2492     @rtype: dict
2493     @return: a dictionary with keys the ssconf names and values their
2494         associated value
2495
2496     """
2497     fn = "\n".join
2498     instance_names = utils.NiceSort(
2499                        [inst.name for inst in
2500                         self._UnlockedGetAllInstancesInfo().values()])
2501     node_infos = self._UnlockedGetAllNodesInfo().values()
2502     node_names = [node.name for node in node_infos]
2503     node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2504                     for ninfo in node_infos]
2505     node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2506                     for ninfo in node_infos]
2507
2508     instance_data = fn(instance_names)
2509     off_data = fn(node.name for node in node_infos if node.offline)
2510     on_data = fn(node.name for node in node_infos if not node.offline)
2511     mc_data = fn(node.name for node in node_infos if node.master_candidate)
2512     mc_ips_data = fn(node.primary_ip for node in node_infos
2513                      if node.master_candidate)
2514     node_data = fn(node_names)
2515     node_pri_ips_data = fn(node_pri_ips)
2516     node_snd_ips_data = fn(node_snd_ips)
2517
2518     cluster = self._config_data.cluster
2519     cluster_tags = fn(cluster.GetTags())
2520
2521     hypervisor_list = fn(cluster.enabled_hypervisors)
2522     all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2523
2524     uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2525
2526     nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2527                   self._config_data.nodegroups.values()]
2528     nodegroups_data = fn(utils.NiceSort(nodegroups))
2529     networks = ["%s %s" % (net.uuid, net.name) for net in
2530                 self._config_data.networks.values()]
2531     networks_data = fn(utils.NiceSort(networks))
2532
2533     ssconf_values = {
2534       constants.SS_CLUSTER_NAME: cluster.cluster_name,
2535       constants.SS_CLUSTER_TAGS: cluster_tags,
2536       constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2537       constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2538       constants.SS_MASTER_CANDIDATES: mc_data,
2539       constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2540       constants.SS_MASTER_IP: cluster.master_ip,
2541       constants.SS_MASTER_NETDEV: cluster.master_netdev,
2542       constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2543       constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2544       constants.SS_NODE_LIST: node_data,
2545       constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2546       constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2547       constants.SS_OFFLINE_NODES: off_data,
2548       constants.SS_ONLINE_NODES: on_data,
2549       constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2550       constants.SS_INSTANCE_LIST: instance_data,
2551       constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2552       constants.SS_HYPERVISOR_LIST: hypervisor_list,
2553       constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2554       constants.SS_UID_POOL: uid_pool,
2555       constants.SS_NODEGROUPS: nodegroups_data,
2556       constants.SS_NETWORKS: networks_data,
2557       }
2558     ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2559                                                      all_hvparams)
2560     bad_values = [(k, v) for k, v in ssconf_values.items()
2561                   if not isinstance(v, (str, basestring))]
2562     if bad_values:
2563       err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2564       raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2565                                       " values: %s" % err)
2566     return ssconf_values
2567
2568   @locking.ssynchronized(_config_lock, shared=1)
2569   def GetSsconfValues(self):
2570     """Wrapper using lock around _UnlockedGetSsconf().
2571
2572     """
2573     return self._UnlockedGetSsconfValues()
2574
2575   @locking.ssynchronized(_config_lock, shared=1)
2576   def GetVGName(self):
2577     """Return the volume group name.
2578
2579     """
2580     return self._config_data.cluster.volume_group_name
2581
2582   @locking.ssynchronized(_config_lock)
2583   def SetVGName(self, vg_name):
2584     """Set the volume group name.
2585
2586     """
2587     self._config_data.cluster.volume_group_name = vg_name
2588     self._config_data.cluster.serial_no += 1
2589     self._WriteConfig()
2590
2591   @locking.ssynchronized(_config_lock, shared=1)
2592   def GetDRBDHelper(self):
2593     """Return DRBD usermode helper.
2594
2595     """
2596     return self._config_data.cluster.drbd_usermode_helper
2597
2598   @locking.ssynchronized(_config_lock)
2599   def SetDRBDHelper(self, drbd_helper):
2600     """Set DRBD usermode helper.
2601
2602     """
2603     self._config_data.cluster.drbd_usermode_helper = drbd_helper
2604     self._config_data.cluster.serial_no += 1
2605     self._WriteConfig()
2606
2607   @locking.ssynchronized(_config_lock, shared=1)
2608   def GetMACPrefix(self):
2609     """Return the mac prefix.
2610
2611     """
2612     return self._config_data.cluster.mac_prefix
2613
2614   @locking.ssynchronized(_config_lock, shared=1)
2615   def GetClusterInfo(self):
2616     """Returns information about the cluster
2617
2618     @rtype: L{objects.Cluster}
2619     @return: the cluster object
2620
2621     """
2622     return self._config_data.cluster
2623
2624   @locking.ssynchronized(_config_lock, shared=1)
2625   def HasAnyDiskOfType(self, dev_type):
2626     """Check if in there is at disk of the given type in the configuration.
2627
2628     """
2629     return self._config_data.HasAnyDiskOfType(dev_type)
2630
2631   @locking.ssynchronized(_config_lock)
2632   def Update(self, target, feedback_fn, ec_id=None):
2633     """Notify function to be called after updates.
2634
2635     This function must be called when an object (as returned by
2636     GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2637     caller wants the modifications saved to the backing store. Note
2638     that all modified objects will be saved, but the target argument
2639     is the one the caller wants to ensure that it's saved.
2640
2641     @param target: an instance of either L{objects.Cluster},
2642         L{objects.Node} or L{objects.Instance} which is existing in
2643         the cluster
2644     @param feedback_fn: Callable feedback function
2645
2646     """
2647     if self._config_data is None:
2648       raise errors.ProgrammerError("Configuration file not read,"
2649                                    " cannot save.")
2650     update_serial = False
2651     if isinstance(target, objects.Cluster):
2652       test = target == self._config_data.cluster
2653     elif isinstance(target, objects.Node):
2654       test = target in self._config_data.nodes.values()
2655       update_serial = True
2656     elif isinstance(target, objects.Instance):
2657       test = target in self._config_data.instances.values()
2658     elif isinstance(target, objects.NodeGroup):
2659       test = target in self._config_data.nodegroups.values()
2660     elif isinstance(target, objects.Network):
2661       test = target in self._config_data.networks.values()
2662     else:
2663       raise errors.ProgrammerError("Invalid object type (%s) passed to"
2664                                    " ConfigWriter.Update" % type(target))
2665     if not test:
2666       raise errors.ConfigurationError("Configuration updated since object"
2667                                       " has been read or unknown object")
2668     target.serial_no += 1
2669     target.mtime = now = time.time()
2670
2671     if update_serial:
2672       # for node updates, we need to increase the cluster serial too
2673       self._config_data.cluster.serial_no += 1
2674       self._config_data.cluster.mtime = now
2675
2676     if isinstance(target, objects.Instance):
2677       self._UnlockedReleaseDRBDMinors(target.uuid)
2678
2679     if ec_id is not None:
2680       # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2681       self._UnlockedCommitTemporaryIps(ec_id)
2682
2683     self._WriteConfig(feedback_fn=feedback_fn)
2684
2685   @locking.ssynchronized(_config_lock)
2686   def DropECReservations(self, ec_id):
2687     """Drop per-execution-context reservations
2688
2689     """
2690     for rm in self._all_rms:
2691       rm.DropECReservations(ec_id)
2692
2693   @locking.ssynchronized(_config_lock, shared=1)
2694   def GetAllNetworksInfo(self):
2695     """Get configuration info of all the networks.
2696
2697     """
2698     return dict(self._config_data.networks)
2699
2700   def _UnlockedGetNetworkList(self):
2701     """Get the list of networks.
2702
2703     This function is for internal use, when the config lock is already held.
2704
2705     """
2706     return self._config_data.networks.keys()
2707
2708   @locking.ssynchronized(_config_lock, shared=1)
2709   def GetNetworkList(self):
2710     """Get the list of networks.
2711
2712     @return: array of networks, ex. ["main", "vlan100", "200]
2713
2714     """
2715     return self._UnlockedGetNetworkList()
2716
2717   @locking.ssynchronized(_config_lock, shared=1)
2718   def GetNetworkNames(self):
2719     """Get a list of network names
2720
2721     """
2722     names = [net.name
2723              for net in self._config_data.networks.values()]
2724     return names
2725
2726   def _UnlockedGetNetwork(self, uuid):
2727     """Returns information about a network.
2728
2729     This function is for internal use, when the config lock is already held.
2730
2731     """
2732     if uuid not in self._config_data.networks:
2733       return None
2734
2735     return self._config_data.networks[uuid]
2736
2737   @locking.ssynchronized(_config_lock, shared=1)
2738   def GetNetwork(self, uuid):
2739     """Returns information about a network.
2740
2741     It takes the information from the configuration file.
2742
2743     @param uuid: UUID of the network
2744
2745     @rtype: L{objects.Network}
2746     @return: the network object
2747
2748     """
2749     return self._UnlockedGetNetwork(uuid)
2750
2751   @locking.ssynchronized(_config_lock)
2752   def AddNetwork(self, net, ec_id, check_uuid=True):
2753     """Add a network to the configuration.
2754
2755     @type net: L{objects.Network}
2756     @param net: the Network object to add
2757     @type ec_id: string
2758     @param ec_id: unique id for the job to use when creating a missing UUID
2759
2760     """
2761     self._UnlockedAddNetwork(net, ec_id, check_uuid)
2762     self._WriteConfig()
2763
2764   def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2765     """Add a network to the configuration.
2766
2767     """
2768     logging.info("Adding network %s to configuration", net.name)
2769
2770     if check_uuid:
2771       self._EnsureUUID(net, ec_id)
2772
2773     net.serial_no = 1
2774     net.ctime = net.mtime = time.time()
2775     self._config_data.networks[net.uuid] = net
2776     self._config_data.cluster.serial_no += 1
2777
2778   def _UnlockedLookupNetwork(self, target):
2779     """Lookup a network's UUID.
2780
2781     @type target: string
2782     @param target: network name or UUID
2783     @rtype: string
2784     @return: network UUID
2785     @raises errors.OpPrereqError: when the target network cannot be found
2786
2787     """
2788     if target is None:
2789       return None
2790     if target in self._config_data.networks:
2791       return target
2792     for net in self._config_data.networks.values():
2793       if net.name == target:
2794         return net.uuid
2795     raise errors.OpPrereqError("Network '%s' not found" % target,
2796                                errors.ECODE_NOENT)
2797
2798   @locking.ssynchronized(_config_lock, shared=1)
2799   def LookupNetwork(self, target):
2800     """Lookup a network's UUID.
2801
2802     This function is just a wrapper over L{_UnlockedLookupNetwork}.
2803
2804     @type target: string
2805     @param target: network name or UUID
2806     @rtype: string
2807     @return: network UUID
2808
2809     """
2810     return self._UnlockedLookupNetwork(target)
2811
2812   @locking.ssynchronized(_config_lock)
2813   def RemoveNetwork(self, network_uuid):
2814     """Remove a network from the configuration.
2815
2816     @type network_uuid: string
2817     @param network_uuid: the UUID of the network to remove
2818
2819     """
2820     logging.info("Removing network %s from configuration", network_uuid)
2821
2822     if network_uuid not in self._config_data.networks:
2823       raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2824
2825     del self._config_data.networks[network_uuid]
2826     self._config_data.cluster.serial_no += 1
2827     self._WriteConfig()
2828
2829   def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2830     """Get the netparams (mode, link) of a network.
2831
2832     Get a network's netparams for a given node.
2833
2834     @type net_uuid: string
2835     @param net_uuid: network uuid
2836     @type node_uuid: string
2837     @param node_uuid: node UUID
2838     @rtype: dict or None
2839     @return: netparams
2840
2841     """
2842     node_info = self._UnlockedGetNodeInfo(node_uuid)
2843     nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2844     netparams = nodegroup_info.networks.get(net_uuid, None)
2845
2846     return netparams
2847
2848   @locking.ssynchronized(_config_lock, shared=1)
2849   def GetGroupNetParams(self, net_uuid, node_uuid):
2850     """Locking wrapper of _UnlockedGetGroupNetParams()
2851
2852     """
2853     return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2854
2855   @locking.ssynchronized(_config_lock, shared=1)
2856   def CheckIPInNodeGroup(self, ip, node_uuid):
2857     """Check IP uniqueness in nodegroup.
2858
2859     Check networks that are connected in the node's node group
2860     if ip is contained in any of them. Used when creating/adding
2861     a NIC to ensure uniqueness among nodegroups.
2862
2863     @type ip: string
2864     @param ip: ip address
2865     @type node_uuid: string
2866     @param node_uuid: node UUID
2867     @rtype: (string, dict) or (None, None)
2868     @return: (network name, netparams)
2869
2870     """
2871     if ip is None:
2872       return (None, None)
2873     node_info = self._UnlockedGetNodeInfo(node_uuid)
2874     nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2875     for net_uuid in nodegroup_info.networks.keys():
2876       net_info = self._UnlockedGetNetwork(net_uuid)
2877       pool = network.AddressPool(net_info)
2878       if pool.Contains(ip):
2879         return (net_info.name, nodegroup_info.networks[net_uuid])
2880
2881     return (None, None)