Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ dd7ea762

History | View | Annotate | Download (72.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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-msg=R0904
35
# R0904: Too many public methods
36

    
37
import os
38
import random
39
import logging
40
import time
41

    
42
from ganeti import errors
43
from ganeti import locking
44
from ganeti import utils
45
from ganeti import constants
46
from ganeti import rpc
47
from ganeti import objects
48
from ganeti import serializer
49
from ganeti import uidpool
50
from ganeti import netutils
51
from ganeti import runtime
52
from ganeti import network
53

    
54

    
55
_config_lock = locking.SharedLock("ConfigWriter")
56

    
57
# job id used for resource management at config upgrade time
58
_UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
59

    
60

    
61
def _ValidateConfig(data):
62
  """Verifies that a configuration objects looks valid.
63

64
  This only verifies the version of the configuration.
65

66
  @raise errors.ConfigurationError: if the version differs from what
67
      we expect
68

69
  """
70
  if data.version != constants.CONFIG_VERSION:
71
    raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
72

    
73

    
74
class TemporaryReservationManager:
75
  """A temporary resource reservation manager.
76

77
  This is used to reserve resources in a job, before using them, making sure
78
  other jobs cannot get them in the meantime.
79

80
  """
81
  def __init__(self):
82
    self._ec_reserved = {}
83

    
84
  def Reserved(self, resource):
85
    for holder_reserved in self._ec_reserved.values():
86
      if resource in holder_reserved:
87
        return True
88
    return False
89

    
90
  def Reserve(self, ec_id, resource):
91
    if self.Reserved(resource):
92
      raise errors.ReservationError("Duplicate reservation for resource '%s'"
93
                                    % str(resource))
94
    if ec_id not in self._ec_reserved:
95
      self._ec_reserved[ec_id] = set([resource])
96
    else:
97
      self._ec_reserved[ec_id].add(resource)
98

    
99
  def DropECReservations(self, ec_id):
100
    if ec_id in self._ec_reserved:
101
      del self._ec_reserved[ec_id]
102

    
103
  def GetReserved(self):
104
    all_reserved = set()
105
    for holder_reserved in self._ec_reserved.values():
106
      all_reserved.update(holder_reserved)
107
    return all_reserved
108

    
109
  def GetECReserved(self, ec_id):
110
    ec_reserved = set()
111
    if ec_id in self._ec_reserved:
112
      ec_reserved.update(self._ec_reserved[ec_id])
113
    return ec_reserved
114

    
115
  def Generate(self, existing, generate_one_fn, ec_id):
116
    """Generate a new resource of this type
117

118
    """
119
    assert callable(generate_one_fn)
120

    
121
    all_elems = self.GetReserved()
122
    all_elems.update(existing)
123
    retries = 64
124
    while retries > 0:
125
      new_resource = generate_one_fn()
126
      if new_resource is not None and new_resource not in all_elems:
127
        break
128
      retries -= 1
129
    else:
130
      raise errors.ConfigurationError("Not able generate new resource"
131
                                      " (last tried: %s)" % new_resource)
132
    self.Reserve(ec_id, new_resource)
133
    return new_resource
134

    
135

    
136
class ConfigWriter:
137
  """The interface to the cluster configuration.
138

139
  @ivar _temporary_lvs: reservation manager for temporary LVs
140
  @ivar _all_rms: a list of all temporary reservation managers
141

142
  """
143
  def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
144
               accept_foreign=False):
145
    self.write_count = 0
146
    self._lock = _config_lock
147
    self._config_data = None
148
    self._offline = offline
149
    if cfg_file is None:
150
      self._cfg_file = constants.CLUSTER_CONF_FILE
151
    else:
152
      self._cfg_file = cfg_file
153
    self._getents = _getents
154
    self._temporary_ids = TemporaryReservationManager()
155
    self._temporary_drbds = {}
156
    self._temporary_macs = TemporaryReservationManager()
157
    self._temporary_secrets = TemporaryReservationManager()
158
    self._temporary_lvs = TemporaryReservationManager()
159
    self._temporary_ips = TemporaryReservationManager()
160
    self._all_rms = [self._temporary_ids, self._temporary_macs,
161
                     self._temporary_secrets, self._temporary_lvs,
162
                     self._temporary_ips]
163
    # Note: in order to prevent errors when resolving our name in
164
    # _DistributeConfig, we compute it here once and reuse it; it's
165
    # better to raise an error before starting to modify the config
166
    # file than after it was modified
167
    self._my_hostname = netutils.Hostname.GetSysName()
168
    self._last_cluster_serial = -1
169
    self._cfg_id = None
170
    self._OpenConfig(accept_foreign)
171

    
172
  # this method needs to be static, so that we can call it on the class
173
  @staticmethod
174
  def IsCluster():
175
    """Check if the cluster is configured.
176

177
    """
178
    return os.path.exists(constants.CLUSTER_CONF_FILE)
179

    
180
  def _GenerateOneMAC(self):
181
    """Generate one mac address
182

183
    """
184
    prefix = self._config_data.cluster.mac_prefix
185
    byte1 = random.randrange(0, 256)
186
    byte2 = random.randrange(0, 256)
187
    byte3 = random.randrange(0, 256)
188
    mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
189
    return mac
190

    
191
  @locking.ssynchronized(_config_lock, shared=1)
192
  def GetNdParams(self, node):
193
    """Get the node params populated with cluster defaults.
194

195
    @type node: L{object.Node}
196
    @param node: The node we want to know the params for
197
    @return: A dict with the filled in node params
198

199
    """
200
    nodegroup = self._UnlockedGetNodeGroup(node.group)
201
    return self._config_data.cluster.FillND(node, nodegroup)
202

    
203
  @locking.ssynchronized(_config_lock, shared=1)
204
  def GenerateMAC(self, ec_id):
205
    """Generate a MAC for an instance.
206

207
    This should check the current instances for duplicates.
208

209
    """
210
    existing = self._AllMACs()
211
    return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
212

    
213
  @locking.ssynchronized(_config_lock, shared=1)
214
  def ReserveMAC(self, mac, ec_id):
215
    """Reserve a MAC for an instance.
216

217
    This only checks instances managed by this cluster, it does not
218
    check for potential collisions elsewhere.
219

220
    """
221
    all_macs = self._AllMACs()
222
    if mac in all_macs:
223
      raise errors.ReservationError("mac already in use")
224
    else:
225
      self._temporary_macs.Reserve(ec_id, mac)
226

    
227
  def _UnlockedCommitReservedIps(self, ec_id):
228
    """Commit all reserved IP address to their respective pools
229

230
    """
231
    for address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
232
      self._UnlockedCommitIp(net_uuid, address)
233

    
234
  def _UnlockedCommitIp(self, net_uuid, address):
235
    """Commit a reserved IP address to an IP pool.
236

237
    The IP address is taken from the IP pool designated by link and marked
238
    as reserved.
239

240
    """
241
    nobj = self._UnlockedGetNetwork(net_uuid)
242
    pool = network.AddressPool(nobj)
243
    pool.Reserve(address)
244

    
245
  @locking.ssynchronized(_config_lock)
246
  def CommitIp(self, net_uuid, address):
247
    """Commit a reserved IP to an IP pool
248

249
    This is just a wrapper around _UnlockedCommitIp.
250

251
    @param net_uuid: UUID of the network to commit the IP to
252
    @param address: The IP address
253

254
    """
255
    self._UnlockedCommitIp(net_uuid, address)
256
    self._WriteConfig()
257

    
258
  @locking.ssynchronized(_config_lock)
259
  def CommitGroupInstanceIps(self, group_uuid, net_uuid,
260
                             link, feedback_fn=None):
261
    """Commit all IPs of instances on a given node group's link to the pools.
262

263
    This is used when mapping networks to node groups, and is a separate method
264
    to ensure atomicity (i.e. all or none commited).
265

266
    @param group_uuid: the uuid of the node group
267
    @param net_uuid: the uuid of the network to use
268
    @param link: the link on which the relevant instances reside
269

270
    """
271
    affected_nodes = []
272
    for node, ni in self._config_data.nodes.items():
273
      if ni.group == group_uuid:
274
        affected_nodes.append(node)
275

    
276
    for instance in self._config_data.instances.values():
277
      if instance.primary_node not in affected_nodes:
278
        continue
279

    
280
      for nic in instance.nics:
281
        nic_link = nic.nicparams.get(constants.NIC_LINK, None)
282
        if nic_link == link:
283
          if feedback_fn:
284
            feedback_fn("Commiting instance %s IP %s" % (instance.name, nic.ip))
285
          self._UnlockedCommitIp(net_uuid, nic.ip)
286

    
287
    self._WriteConfig()
288

    
289
  def _UnlockedReleaseIp(self, net_uuid, address):
290
    """Give a specific IP address back to an IP pool.
291

292
    The IP address is returned to the IP pool designated by pool_id and marked
293
    as reserved.
294

295
    """
296
    nobj = self._UnlockedGetNetwork(net_uuid)
297
    pool = network.AddressPool(nobj)
298
    pool.Release(address)
299

    
300
  @locking.ssynchronized(_config_lock)
301
  def ReleaseIp(self, node_name, link, address):
302
    """Give a specified IP address back to an IP pool.
303

304
    This is just a wrapper around _UnlockedReleaseIp.
305

306
    """
307
    net_uuid = self._UnlockedGetNetworkFromNodeLink(node_name, link)
308
    if not net_uuid:
309
      return
310
    self._UnlockedReleaseIp(net_uuid, address)
311
    self._WriteConfig()
312

    
313
  @locking.ssynchronized(_config_lock)
314
  def ReleaseGroupInstanceIps(self, group_uuid, net_uuid,
315
                              link, feedback_fn=None):
316
    """Commit all IPs of instances on a given node group's link to the pools.
317

318
    This is used when unmapping networks from node groups and
319
    is a separate method to ensure atomicity (i.e. all or none commited).
320

321
    @param group_uuid: the uuid of the node group
322
    @param net_uuid: the uuid of the network to use
323
    @param link: the link on which the relevant instances reside
324

325
    """
326
    affected_nodes = []
327
    for node, ni in self._config_data.nodes.items():
328
      if ni.group == group_uuid:
329
        affected_nodes.append(node)
330

    
331
    for instance in self._config_data.instances.values():
332
      if instance.primary_node not in affected_nodes:
333
        continue
334

    
335
      for nic in instance.nics:
336
        nic_link = nic.nicparams.get(constants.NIC_LINK, None)
337
        if nic_link == link:
338
          if feedback_fn:
339
            feedback_fn("Releasing instance %s IP %s" % (instance.name, nic.ip))
340
          self._UnlockedReleaseIp(net_uuid, nic.ip)
341

    
342
    self._WriteConfig()
343

    
344
  @locking.ssynchronized(_config_lock, shared=1)
345
  def GenerateIp(self, node_name, link, ec_id):
346
    """Find a free IPv4 address for an instance.
347

348
    """
349
    net_uuid = self._UnlockedGetNetworkFromNodeLink(node_name, link)
350
    nobj = self._UnlockedGetNetwork(net_uuid)
351
    pool = network.AddressPool(nobj)
352
    gen_free = pool.GenerateFree()
353
    gen_one = lambda: (gen_free(), net_uuid)
354
    address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
355
    return address
356

    
357
  @locking.ssynchronized(_config_lock, shared=1)
358
  def ReserveIp(self, node_name, link, address, ec_id):
359
    """Reserve a given IPv4 address for use by an instance.
360

361
    """
362
    net_uuid = self._UnlockedGetNetworkFromNodeLink(node_name, link)
363
    nobj = self._UnlockedGetNetwork(net_uuid)
364
    pool = network.AddressPool(nobj)
365
    try:
366
      pool.Reserve(address)
367
    except errors.AddressPoolError:
368
      raise errors.ReservationError("IP address already in use")
369
    return self._temporary_ips.Reserve((address, net_uuid), ec_id)
370

    
371
  @locking.ssynchronized(_config_lock, shared=1)
372
  def ReserveLV(self, lv_name, ec_id):
373
    """Reserve an VG/LV pair for an instance.
374

375
    @type lv_name: string
376
    @param lv_name: the logical volume name to reserve
377

378
    """
379
    all_lvs = self._AllLVs()
380
    if lv_name in all_lvs:
381
      raise errors.ReservationError("LV already in use")
382
    else:
383
      self._temporary_lvs.Reserve(ec_id, lv_name)
384

    
385
  @locking.ssynchronized(_config_lock, shared=1)
386
  def GenerateDRBDSecret(self, ec_id):
387
    """Generate a DRBD secret.
388

389
    This checks the current disks for duplicates.
390

391
    """
392
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
393
                                            utils.GenerateSecret,
394
                                            ec_id)
395

    
396
  def _AllLVs(self):
397
    """Compute the list of all LVs.
398

399
    """
400
    lvnames = set()
401
    for instance in self._config_data.instances.values():
402
      node_data = instance.MapLVsByNode()
403
      for lv_list in node_data.values():
404
        lvnames.update(lv_list)
405
    return lvnames
406

    
407
  def _AllIDs(self, include_temporary):
408
    """Compute the list of all UUIDs and names we have.
409

410
    @type include_temporary: boolean
411
    @param include_temporary: whether to include the _temporary_ids set
412
    @rtype: set
413
    @return: a set of IDs
414

415
    """
416
    existing = set()
417
    if include_temporary:
418
      existing.update(self._temporary_ids.GetReserved())
419
    existing.update(self._AllLVs())
420
    existing.update(self._config_data.instances.keys())
421
    existing.update(self._config_data.nodes.keys())
422
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
423
    return existing
424

    
425
  def _GenerateUniqueID(self, ec_id):
426
    """Generate an unique UUID.
427

428
    This checks the current node, instances and disk names for
429
    duplicates.
430

431
    @rtype: string
432
    @return: the unique id
433

434
    """
435
    existing = self._AllIDs(include_temporary=False)
436
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
437

    
438
  @locking.ssynchronized(_config_lock, shared=1)
439
  def GenerateUniqueID(self, ec_id):
440
    """Generate an unique ID.
441

442
    This is just a wrapper over the unlocked version.
443

444
    @type ec_id: string
445
    @param ec_id: unique id for the job to reserve the id to
446

447
    """
448
    return self._GenerateUniqueID(ec_id)
449

    
450
  def _AllMACs(self):
451
    """Return all MACs present in the config.
452

453
    @rtype: list
454
    @return: the list of all MACs
455

456
    """
457
    result = []
458
    for instance in self._config_data.instances.values():
459
      for nic in instance.nics:
460
        result.append(nic.mac)
461

    
462
    return result
463

    
464
  def _AllDRBDSecrets(self):
465
    """Return all DRBD secrets present in the config.
466

467
    @rtype: list
468
    @return: the list of all DRBD secrets
469

470
    """
471
    def helper(disk, result):
472
      """Recursively gather secrets from this disk."""
473
      if disk.dev_type == constants.DT_DRBD8:
474
        result.append(disk.logical_id[5])
475
      if disk.children:
476
        for child in disk.children:
477
          helper(child, result)
478

    
479
    result = []
480
    for instance in self._config_data.instances.values():
481
      for disk in instance.disks:
482
        helper(disk, result)
483

    
484
    return result
485

    
486
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
487
    """Compute duplicate disk IDs
488

489
    @type disk: L{objects.Disk}
490
    @param disk: the disk at which to start searching
491
    @type l_ids: list
492
    @param l_ids: list of current logical ids
493
    @type p_ids: list
494
    @param p_ids: list of current physical ids
495
    @rtype: list
496
    @return: a list of error messages
497

498
    """
499
    result = []
500
    if disk.logical_id is not None:
501
      if disk.logical_id in l_ids:
502
        result.append("duplicate logical id %s" % str(disk.logical_id))
503
      else:
504
        l_ids.append(disk.logical_id)
505
    if disk.physical_id is not None:
506
      if disk.physical_id in p_ids:
507
        result.append("duplicate physical id %s" % str(disk.physical_id))
508
      else:
509
        p_ids.append(disk.physical_id)
510

    
511
    if disk.children:
512
      for child in disk.children:
513
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
514
    return result
515

    
516
  def _UnlockedVerifyConfig(self):
517
    """Verify function.
518

519
    @rtype: list
520
    @return: a list of error messages; a non-empty list signifies
521
        configuration errors
522

523
    """
524
    # pylint: disable-msg=R0914
525
    result = []
526
    seen_macs = []
527
    ports = {}
528
    data = self._config_data
529
    cluster = data.cluster
530
    seen_lids = []
531
    seen_pids = []
532

    
533
    # global cluster checks
534
    if not cluster.enabled_hypervisors:
535
      result.append("enabled hypervisors list doesn't have any entries")
536
    invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
537
    if invalid_hvs:
538
      result.append("enabled hypervisors contains invalid entries: %s" %
539
                    invalid_hvs)
540
    missing_hvp = (set(cluster.enabled_hypervisors) -
541
                   set(cluster.hvparams.keys()))
542
    if missing_hvp:
543
      result.append("hypervisor parameters missing for the enabled"
544
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
545

    
546
    if cluster.master_node not in data.nodes:
547
      result.append("cluster has invalid primary node '%s'" %
548
                    cluster.master_node)
549

    
550
    def _helper(owner, attr, value, template):
551
      try:
552
        utils.ForceDictType(value, template)
553
      except errors.GenericError, err:
554
        result.append("%s has invalid %s: %s" % (owner, attr, err))
555

    
556
    def _helper_nic(owner, params):
557
      try:
558
        objects.NIC.CheckParameterSyntax(params)
559
      except errors.ConfigurationError, err:
560
        result.append("%s has invalid nicparams: %s" % (owner, err))
561

    
562
    # check cluster parameters
563
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
564
            constants.BES_PARAMETER_TYPES)
565
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
566
            constants.NICS_PARAMETER_TYPES)
567
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
568
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
569
            constants.NDS_PARAMETER_TYPES)
570

    
571
    # per-instance checks
572
    for instance_name in data.instances:
573
      instance = data.instances[instance_name]
574
      if instance.name != instance_name:
575
        result.append("instance '%s' is indexed by wrong name '%s'" %
576
                      (instance.name, instance_name))
577
      if instance.primary_node not in data.nodes:
578
        result.append("instance '%s' has invalid primary node '%s'" %
579
                      (instance_name, instance.primary_node))
580
      for snode in instance.secondary_nodes:
581
        if snode not in data.nodes:
582
          result.append("instance '%s' has invalid secondary node '%s'" %
583
                        (instance_name, snode))
584
      for idx, nic in enumerate(instance.nics):
585
        if nic.mac in seen_macs:
586
          result.append("instance '%s' has NIC %d mac %s duplicate" %
587
                        (instance_name, idx, nic.mac))
588
        else:
589
          seen_macs.append(nic.mac)
590
        if nic.nicparams:
591
          filled = cluster.SimpleFillNIC(nic.nicparams)
592
          owner = "instance %s nic %d" % (instance.name, idx)
593
          _helper(owner, "nicparams",
594
                  filled, constants.NICS_PARAMETER_TYPES)
595
          _helper_nic(owner, filled)
596

    
597
      # parameter checks
598
      if instance.beparams:
599
        _helper("instance %s" % instance.name, "beparams",
600
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
601

    
602
      # gather the drbd ports for duplicate checks
603
      for dsk in instance.disks:
604
        if dsk.dev_type in constants.LDS_DRBD:
605
          tcp_port = dsk.logical_id[2]
606
          if tcp_port not in ports:
607
            ports[tcp_port] = []
608
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
609
      # gather network port reservation
610
      net_port = getattr(instance, "network_port", None)
611
      if net_port is not None:
612
        if net_port not in ports:
613
          ports[net_port] = []
614
        ports[net_port].append((instance.name, "network port"))
615

    
616
      # instance disk verify
617
      for idx, disk in enumerate(instance.disks):
618
        result.extend(["instance '%s' disk %d error: %s" %
619
                       (instance.name, idx, msg) for msg in disk.Verify()])
620
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
621

    
622
    # cluster-wide pool of free ports
623
    for free_port in cluster.tcpudp_port_pool:
624
      if free_port not in ports:
625
        ports[free_port] = []
626
      ports[free_port].append(("cluster", "port marked as free"))
627

    
628
    # compute tcp/udp duplicate ports
629
    keys = ports.keys()
630
    keys.sort()
631
    for pnum in keys:
632
      pdata = ports[pnum]
633
      if len(pdata) > 1:
634
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
635
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
636

    
637
    # highest used tcp port check
638
    if keys:
639
      if keys[-1] > cluster.highest_used_port:
640
        result.append("Highest used port mismatch, saved %s, computed %s" %
641
                      (cluster.highest_used_port, keys[-1]))
642

    
643
    if not data.nodes[cluster.master_node].master_candidate:
644
      result.append("Master node is not a master candidate")
645

    
646
    # master candidate checks
647
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
648
    if mc_now < mc_max:
649
      result.append("Not enough master candidates: actual %d, target %d" %
650
                    (mc_now, mc_max))
651

    
652
    # node checks
653
    for node_name, node in data.nodes.items():
654
      if node.name != node_name:
655
        result.append("Node '%s' is indexed by wrong name '%s'" %
656
                      (node.name, node_name))
657
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
658
        result.append("Node %s state is invalid: master_candidate=%s,"
659
                      " drain=%s, offline=%s" %
660
                      (node.name, node.master_candidate, node.drained,
661
                       node.offline))
662
      if node.group not in data.nodegroups:
663
        result.append("Node '%s' has invalid group '%s'" %
664
                      (node.name, node.group))
665
      else:
666
        _helper("node %s" % node.name, "ndparams",
667
                cluster.FillND(node, data.nodegroups[node.group]),
668
                constants.NDS_PARAMETER_TYPES)
669

    
670
    # nodegroups checks
671
    nodegroups_names = set()
672
    for nodegroup_uuid in data.nodegroups:
673
      nodegroup = data.nodegroups[nodegroup_uuid]
674
      if nodegroup.uuid != nodegroup_uuid:
675
        result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
676
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
677
      if utils.UUID_RE.match(nodegroup.name.lower()):
678
        result.append("node group '%s' (uuid: '%s') has uuid-like name" %
679
                      (nodegroup.name, nodegroup.uuid))
680
      if nodegroup.name in nodegroups_names:
681
        result.append("duplicate node group name '%s'" % nodegroup.name)
682
      else:
683
        nodegroups_names.add(nodegroup.name)
684
      if nodegroup.ndparams:
685
        _helper("group %s" % nodegroup.name, "ndparams",
686
                cluster.SimpleFillND(nodegroup.ndparams),
687
                constants.NDS_PARAMETER_TYPES)
688

    
689

    
690
    # drbd minors check
691
    _, duplicates = self._UnlockedComputeDRBDMap()
692
    for node, minor, instance_a, instance_b in duplicates:
693
      result.append("DRBD minor %d on node %s is assigned twice to instances"
694
                    " %s and %s" % (minor, node, instance_a, instance_b))
695

    
696
    # IP checks
697
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
698
    ips = {}
699

    
700
    def _AddIpAddress(ip, name):
701
      ips.setdefault(ip, []).append(name)
702

    
703
    _AddIpAddress(cluster.master_ip, "cluster_ip")
704

    
705
    for node in data.nodes.values():
706
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
707
      if node.secondary_ip != node.primary_ip:
708
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
709

    
710
    for instance in data.instances.values():
711
      for idx, nic in enumerate(instance.nics):
712
        if nic.ip is None:
713
          continue
714

    
715
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
716
        nic_mode = nicparams[constants.NIC_MODE]
717
        nic_link = nicparams[constants.NIC_LINK]
718

    
719
        if nic_mode == constants.NIC_MODE_BRIDGED:
720
          link = "bridge:%s" % nic_link
721
        elif nic_mode == constants.NIC_MODE_ROUTED:
722
          link = "route:%s" % nic_link
723
        else:
724
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
725

    
726
        _AddIpAddress("%s/%s" % (link, nic.ip),
727
                      "instance:%s/nic:%d" % (instance.name, idx))
728

    
729
    for ip, owners in ips.items():
730
      if len(owners) > 1:
731
        result.append("IP address %s is used by multiple owners: %s" %
732
                      (ip, utils.CommaJoin(owners)))
733

    
734
    return result
735

    
736
  @locking.ssynchronized(_config_lock, shared=1)
737
  def VerifyConfig(self):
738
    """Verify function.
739

740
    This is just a wrapper over L{_UnlockedVerifyConfig}.
741

742
    @rtype: list
743
    @return: a list of error messages; a non-empty list signifies
744
        configuration errors
745

746
    """
747
    return self._UnlockedVerifyConfig()
748

    
749
  def _UnlockedSetDiskID(self, disk, node_name):
750
    """Convert the unique ID to the ID needed on the target nodes.
751

752
    This is used only for drbd, which needs ip/port configuration.
753

754
    The routine descends down and updates its children also, because
755
    this helps when the only the top device is passed to the remote
756
    node.
757

758
    This function is for internal use, when the config lock is already held.
759

760
    """
761
    if disk.children:
762
      for child in disk.children:
763
        self._UnlockedSetDiskID(child, node_name)
764

    
765
    if disk.logical_id is None and disk.physical_id is not None:
766
      return
767
    if disk.dev_type == constants.LD_DRBD8:
768
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
769
      if node_name not in (pnode, snode):
770
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
771
                                        node_name)
772
      pnode_info = self._UnlockedGetNodeInfo(pnode)
773
      snode_info = self._UnlockedGetNodeInfo(snode)
774
      if pnode_info is None or snode_info is None:
775
        raise errors.ConfigurationError("Can't find primary or secondary node"
776
                                        " for %s" % str(disk))
777
      p_data = (pnode_info.secondary_ip, port)
778
      s_data = (snode_info.secondary_ip, port)
779
      if pnode == node_name:
780
        disk.physical_id = p_data + s_data + (pminor, secret)
781
      else: # it must be secondary, we tested above
782
        disk.physical_id = s_data + p_data + (sminor, secret)
783
    else:
784
      disk.physical_id = disk.logical_id
785
    return
786

    
787
  @locking.ssynchronized(_config_lock)
788
  def SetDiskID(self, disk, node_name):
789
    """Convert the unique ID to the ID needed on the target nodes.
790

791
    This is used only for drbd, which needs ip/port configuration.
792

793
    The routine descends down and updates its children also, because
794
    this helps when the only the top device is passed to the remote
795
    node.
796

797
    """
798
    return self._UnlockedSetDiskID(disk, node_name)
799

    
800
  @locking.ssynchronized(_config_lock)
801
  def AddTcpUdpPort(self, port):
802
    """Adds a new port to the available port pool.
803

804
    """
805
    if not isinstance(port, int):
806
      raise errors.ProgrammerError("Invalid type passed for port")
807

    
808
    self._config_data.cluster.tcpudp_port_pool.add(port)
809
    self._WriteConfig()
810

    
811
  @locking.ssynchronized(_config_lock, shared=1)
812
  def GetPortList(self):
813
    """Returns a copy of the current port list.
814

815
    """
816
    return self._config_data.cluster.tcpudp_port_pool.copy()
817

    
818
  @locking.ssynchronized(_config_lock)
819
  def AllocatePort(self):
820
    """Allocate a port.
821

822
    The port will be taken from the available port pool or from the
823
    default port range (and in this case we increase
824
    highest_used_port).
825

826
    """
827
    # If there are TCP/IP ports configured, we use them first.
828
    if self._config_data.cluster.tcpudp_port_pool:
829
      port = self._config_data.cluster.tcpudp_port_pool.pop()
830
    else:
831
      port = self._config_data.cluster.highest_used_port + 1
832
      if port >= constants.LAST_DRBD_PORT:
833
        raise errors.ConfigurationError("The highest used port is greater"
834
                                        " than %s. Aborting." %
835
                                        constants.LAST_DRBD_PORT)
836
      self._config_data.cluster.highest_used_port = port
837

    
838
    self._WriteConfig()
839
    return port
840

    
841
  def _UnlockedComputeDRBDMap(self):
842
    """Compute the used DRBD minor/nodes.
843

844
    @rtype: (dict, list)
845
    @return: dictionary of node_name: dict of minor: instance_name;
846
        the returned dict will have all the nodes in it (even if with
847
        an empty list), and a list of duplicates; if the duplicates
848
        list is not empty, the configuration is corrupted and its caller
849
        should raise an exception
850

851
    """
852
    def _AppendUsedPorts(instance_name, disk, used):
853
      duplicates = []
854
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
855
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
856
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
857
          assert node in used, ("Node '%s' of instance '%s' not found"
858
                                " in node list" % (node, instance_name))
859
          if port in used[node]:
860
            duplicates.append((node, port, instance_name, used[node][port]))
861
          else:
862
            used[node][port] = instance_name
863
      if disk.children:
864
        for child in disk.children:
865
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
866
      return duplicates
867

    
868
    duplicates = []
869
    my_dict = dict((node, {}) for node in self._config_data.nodes)
870
    for instance in self._config_data.instances.itervalues():
871
      for disk in instance.disks:
872
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
873
    for (node, minor), instance in self._temporary_drbds.iteritems():
874
      if minor in my_dict[node] and my_dict[node][minor] != instance:
875
        duplicates.append((node, minor, instance, my_dict[node][minor]))
876
      else:
877
        my_dict[node][minor] = instance
878
    return my_dict, duplicates
879

    
880
  @locking.ssynchronized(_config_lock)
881
  def ComputeDRBDMap(self):
882
    """Compute the used DRBD minor/nodes.
883

884
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
885

886
    @return: dictionary of node_name: dict of minor: instance_name;
887
        the returned dict will have all the nodes in it (even if with
888
        an empty list).
889

890
    """
891
    d_map, duplicates = self._UnlockedComputeDRBDMap()
892
    if duplicates:
893
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
894
                                      str(duplicates))
895
    return d_map
896

    
897
  @locking.ssynchronized(_config_lock)
898
  def AllocateDRBDMinor(self, nodes, instance):
899
    """Allocate a drbd minor.
900

901
    The free minor will be automatically computed from the existing
902
    devices. A node can be given multiple times in order to allocate
903
    multiple minors. The result is the list of minors, in the same
904
    order as the passed nodes.
905

906
    @type instance: string
907
    @param instance: the instance for which we allocate minors
908

909
    """
910
    assert isinstance(instance, basestring), \
911
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
912

    
913
    d_map, duplicates = self._UnlockedComputeDRBDMap()
914
    if duplicates:
915
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
916
                                      str(duplicates))
917
    result = []
918
    for nname in nodes:
919
      ndata = d_map[nname]
920
      if not ndata:
921
        # no minors used, we can start at 0
922
        result.append(0)
923
        ndata[0] = instance
924
        self._temporary_drbds[(nname, 0)] = instance
925
        continue
926
      keys = ndata.keys()
927
      keys.sort()
928
      ffree = utils.FirstFree(keys)
929
      if ffree is None:
930
        # return the next minor
931
        # TODO: implement high-limit check
932
        minor = keys[-1] + 1
933
      else:
934
        minor = ffree
935
      # double-check minor against current instances
936
      assert minor not in d_map[nname], \
937
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
938
              " already allocated to instance %s" %
939
              (minor, nname, d_map[nname][minor]))
940
      ndata[minor] = instance
941
      # double-check minor against reservation
942
      r_key = (nname, minor)
943
      assert r_key not in self._temporary_drbds, \
944
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
945
              " reserved for instance %s" %
946
              (minor, nname, self._temporary_drbds[r_key]))
947
      self._temporary_drbds[r_key] = instance
948
      result.append(minor)
949
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
950
                  nodes, result)
951
    return result
952

    
953
  def _UnlockedReleaseDRBDMinors(self, instance):
954
    """Release temporary drbd minors allocated for a given instance.
955

956
    @type instance: string
957
    @param instance: the instance for which temporary minors should be
958
                     released
959

960
    """
961
    assert isinstance(instance, basestring), \
962
           "Invalid argument passed to ReleaseDRBDMinors"
963
    for key, name in self._temporary_drbds.items():
964
      if name == instance:
965
        del self._temporary_drbds[key]
966

    
967
  @locking.ssynchronized(_config_lock)
968
  def ReleaseDRBDMinors(self, instance):
969
    """Release temporary drbd minors allocated for a given instance.
970

971
    This should be called on the error paths, on the success paths
972
    it's automatically called by the ConfigWriter add and update
973
    functions.
974

975
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
976

977
    @type instance: string
978
    @param instance: the instance for which temporary minors should be
979
                     released
980

981
    """
982
    self._UnlockedReleaseDRBDMinors(instance)
983

    
984
  @locking.ssynchronized(_config_lock, shared=1)
985
  def GetConfigVersion(self):
986
    """Get the configuration version.
987

988
    @return: Config version
989

990
    """
991
    return self._config_data.version
992

    
993
  @locking.ssynchronized(_config_lock, shared=1)
994
  def GetClusterName(self):
995
    """Get cluster name.
996

997
    @return: Cluster name
998

999
    """
1000
    return self._config_data.cluster.cluster_name
1001

    
1002
  @locking.ssynchronized(_config_lock, shared=1)
1003
  def GetMasterNode(self):
1004
    """Get the hostname of the master node for this cluster.
1005

1006
    @return: Master hostname
1007

1008
    """
1009
    return self._config_data.cluster.master_node
1010

    
1011
  @locking.ssynchronized(_config_lock, shared=1)
1012
  def GetMasterIP(self):
1013
    """Get the IP of the master node for this cluster.
1014

1015
    @return: Master IP
1016

1017
    """
1018
    return self._config_data.cluster.master_ip
1019

    
1020
  @locking.ssynchronized(_config_lock, shared=1)
1021
  def GetMasterNetdev(self):
1022
    """Get the master network device for this cluster.
1023

1024
    """
1025
    return self._config_data.cluster.master_netdev
1026

    
1027
  @locking.ssynchronized(_config_lock, shared=1)
1028
  def GetFileStorageDir(self):
1029
    """Get the file storage dir for this cluster.
1030

1031
    """
1032
    return self._config_data.cluster.file_storage_dir
1033

    
1034
  @locking.ssynchronized(_config_lock, shared=1)
1035
  def GetSharedFileStorageDir(self):
1036
    """Get the shared file storage dir for this cluster.
1037

1038
    """
1039
    return self._config_data.cluster.shared_file_storage_dir
1040

    
1041
  @locking.ssynchronized(_config_lock, shared=1)
1042
  def GetHypervisorType(self):
1043
    """Get the hypervisor type for this cluster.
1044

1045
    """
1046
    return self._config_data.cluster.enabled_hypervisors[0]
1047

    
1048
  @locking.ssynchronized(_config_lock, shared=1)
1049
  def GetHostKey(self):
1050
    """Return the rsa hostkey from the config.
1051

1052
    @rtype: string
1053
    @return: the rsa hostkey
1054

1055
    """
1056
    return self._config_data.cluster.rsahostkeypub
1057

    
1058
  @locking.ssynchronized(_config_lock, shared=1)
1059
  def GetDefaultIAllocator(self):
1060
    """Get the default instance allocator for this cluster.
1061

1062
    """
1063
    return self._config_data.cluster.default_iallocator
1064

    
1065
  @locking.ssynchronized(_config_lock, shared=1)
1066
  def GetPrimaryIPFamily(self):
1067
    """Get cluster primary ip family.
1068

1069
    @return: primary ip family
1070

1071
    """
1072
    return self._config_data.cluster.primary_ip_family
1073

    
1074
  @locking.ssynchronized(_config_lock)
1075
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1076
    """Add a node group to the configuration.
1077

1078
    This method calls group.UpgradeConfig() to fill any missing attributes
1079
    according to their default values.
1080

1081
    @type group: L{objects.NodeGroup}
1082
    @param group: the NodeGroup object to add
1083
    @type ec_id: string
1084
    @param ec_id: unique id for the job to use when creating a missing UUID
1085
    @type check_uuid: bool
1086
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1087
                       it does, ensure that it does not exist in the
1088
                       configuration already
1089

1090
    """
1091
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1092
    self._WriteConfig()
1093

    
1094
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1095
    """Add a node group to the configuration.
1096

1097
    """
1098
    logging.info("Adding node group %s to configuration", group.name)
1099

    
1100
    # Some code might need to add a node group with a pre-populated UUID
1101
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1102
    # the "does this UUID" exist already check.
1103
    if check_uuid:
1104
      self._EnsureUUID(group, ec_id)
1105

    
1106
    try:
1107
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1108
    except errors.OpPrereqError:
1109
      pass
1110
    else:
1111
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1112
                                 " node group (UUID: %s)" %
1113
                                 (group.name, existing_uuid),
1114
                                 errors.ECODE_EXISTS)
1115

    
1116
    group.serial_no = 1
1117
    group.ctime = group.mtime = time.time()
1118
    group.UpgradeConfig()
1119

    
1120
    self._config_data.nodegroups[group.uuid] = group
1121
    self._config_data.cluster.serial_no += 1
1122

    
1123
  @locking.ssynchronized(_config_lock)
1124
  def RemoveNodeGroup(self, group_uuid):
1125
    """Remove a node group from the configuration.
1126

1127
    @type group_uuid: string
1128
    @param group_uuid: the UUID of the node group to remove
1129

1130
    """
1131
    logging.info("Removing node group %s from configuration", group_uuid)
1132

    
1133
    if group_uuid not in self._config_data.nodegroups:
1134
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1135

    
1136
    assert len(self._config_data.nodegroups) != 1, \
1137
            "Group '%s' is the only group, cannot be removed" % group_uuid
1138

    
1139
    del self._config_data.nodegroups[group_uuid]
1140
    self._config_data.cluster.serial_no += 1
1141
    self._WriteConfig()
1142

    
1143
  def _UnlockedLookupNodeGroup(self, target):
1144
    """Lookup a node group's UUID.
1145

1146
    @type target: string or None
1147
    @param target: group name or UUID or None to look for the default
1148
    @rtype: string
1149
    @return: nodegroup UUID
1150
    @raises errors.OpPrereqError: when the target group cannot be found
1151

1152
    """
1153
    if target is None:
1154
      if len(self._config_data.nodegroups) != 1:
1155
        raise errors.OpPrereqError("More than one node group exists. Target"
1156
                                   " group must be specified explicitely.")
1157
      else:
1158
        return self._config_data.nodegroups.keys()[0]
1159
    if target in self._config_data.nodegroups:
1160
      return target
1161
    for nodegroup in self._config_data.nodegroups.values():
1162
      if nodegroup.name == target:
1163
        return nodegroup.uuid
1164
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1165
                               errors.ECODE_NOENT)
1166

    
1167
  @locking.ssynchronized(_config_lock, shared=1)
1168
  def LookupNodeGroup(self, target):
1169
    """Lookup a node group's UUID.
1170

1171
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1172

1173
    @type target: string or None
1174
    @param target: group name or UUID or None to look for the default
1175
    @rtype: string
1176
    @return: nodegroup UUID
1177

1178
    """
1179
    return self._UnlockedLookupNodeGroup(target)
1180

    
1181
  def _UnlockedGetNodeGroup(self, uuid):
1182
    """Lookup a node group.
1183

1184
    @type uuid: string
1185
    @param uuid: group UUID
1186
    @rtype: L{objects.NodeGroup} or None
1187
    @return: nodegroup object, or None if not found
1188

1189
    """
1190
    if uuid not in self._config_data.nodegroups:
1191
      return None
1192

    
1193
    return self._config_data.nodegroups[uuid]
1194

    
1195
  @locking.ssynchronized(_config_lock, shared=1)
1196
  def GetNodeGroup(self, uuid):
1197
    """Lookup a node group.
1198

1199
    @type uuid: string
1200
    @param uuid: group UUID
1201
    @rtype: L{objects.NodeGroup} or None
1202
    @return: nodegroup object, or None if not found
1203

1204
    """
1205
    return self._UnlockedGetNodeGroup(uuid)
1206

    
1207
  @locking.ssynchronized(_config_lock, shared=1)
1208
  def GetAllNodeGroupsInfo(self):
1209
    """Get the configuration of all node groups.
1210

1211
    """
1212
    return dict(self._config_data.nodegroups)
1213

    
1214
  @locking.ssynchronized(_config_lock, shared=1)
1215
  def GetNodeGroupList(self):
1216
    """Get a list of node groups.
1217

1218
    """
1219
    return self._config_data.nodegroups.keys()
1220

    
1221
  def _UnlockedGetNetworkFromNodeLink(self, node_name, node_link):
1222
    node = self._config_data.nodes[node_name]
1223
    nodegroup = self._UnlockedGetNodeGroup(node.group)
1224
    for uuid, link in nodegroup.networks.items():
1225
      if link == node_link:
1226
        return uuid
1227

    
1228
    return None
1229

    
1230
  @locking.ssynchronized(_config_lock, shared=1)
1231
  def GetNetworkFromNodeLink(self, node_name, node_link):
1232
    return self._UnlockedGetNetworkFromNodeLink(node_name, node_link)
1233

    
1234
  @locking.ssynchronized(_config_lock)
1235
  def AddInstance(self, instance, ec_id):
1236
    """Add an instance to the config.
1237

1238
    This should be used after creating a new instance.
1239

1240
    @type instance: L{objects.Instance}
1241
    @param instance: the instance object
1242

1243
    """
1244
    if not isinstance(instance, objects.Instance):
1245
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1246

    
1247
    if instance.disk_template != constants.DT_DISKLESS:
1248
      all_lvs = instance.MapLVsByNode()
1249
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1250

    
1251
    all_macs = self._AllMACs()
1252
    for nic in instance.nics:
1253
      if nic.mac in all_macs:
1254
        raise errors.ConfigurationError("Cannot add instance %s:"
1255
                                        " MAC address '%s' already in use." %
1256
                                        (instance.name, nic.mac))
1257

    
1258
    self._EnsureUUID(instance, ec_id)
1259

    
1260
    instance.serial_no = 1
1261
    instance.ctime = instance.mtime = time.time()
1262
    self._config_data.instances[instance.name] = instance
1263
    self._config_data.cluster.serial_no += 1
1264
    self._UnlockedReleaseDRBDMinors(instance.name)
1265
    self._UnlockedCommitReservedIps(ec_id)
1266
    self._WriteConfig()
1267

    
1268
  def _EnsureUUID(self, item, ec_id):
1269
    """Ensures a given object has a valid UUID.
1270

1271
    @param item: the instance or node to be checked
1272
    @param ec_id: the execution context id for the uuid reservation
1273

1274
    """
1275
    if not item.uuid:
1276
      item.uuid = self._GenerateUniqueID(ec_id)
1277
    elif item.uuid in self._AllIDs(include_temporary=True):
1278
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1279
                                      " in use" % (item.name, item.uuid))
1280

    
1281
  def _SetInstanceStatus(self, instance_name, status):
1282
    """Set the instance's status to a given value.
1283

1284
    """
1285
    assert isinstance(status, bool), \
1286
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1287

    
1288
    if instance_name not in self._config_data.instances:
1289
      raise errors.ConfigurationError("Unknown instance '%s'" %
1290
                                      instance_name)
1291
    instance = self._config_data.instances[instance_name]
1292
    if instance.admin_up != status:
1293
      instance.admin_up = status
1294
      instance.serial_no += 1
1295
      instance.mtime = time.time()
1296
      self._WriteConfig()
1297

    
1298
  @locking.ssynchronized(_config_lock)
1299
  def MarkInstanceUp(self, instance_name):
1300
    """Mark the instance status to up in the config.
1301

1302
    """
1303
    self._SetInstanceStatus(instance_name, True)
1304

    
1305
  @locking.ssynchronized(_config_lock)
1306
  def RemoveInstance(self, instance_name):
1307
    """Remove the instance from the configuration.
1308

1309
    """
1310
    if instance_name not in self._config_data.instances:
1311
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1312

    
1313
    instance = self._UnlockedGetInstanceInfo(instance_name)
1314

    
1315
    for nic in instance.nics:
1316
      nicparams = self._config_data.cluster.SimpleFillNIC(nic.nicparams)
1317
      link = nicparams[constants.NIC_LINK]
1318
      net_uuid = self._UnlockedGetNetworkFromNodeLink(instance.primary_node,
1319
                                                      link)
1320
      if net_uuid:
1321
        # Return all IP addresses to the respective address pools
1322
        self._UnlockedReleaseIp(net_uuid, nic.ip)
1323

    
1324
    del self._config_data.instances[instance_name]
1325
    self._config_data.cluster.serial_no += 1
1326
    self._WriteConfig()
1327

    
1328
  @locking.ssynchronized(_config_lock)
1329
  def RenameInstance(self, old_name, new_name):
1330
    """Rename an instance.
1331

1332
    This needs to be done in ConfigWriter and not by RemoveInstance
1333
    combined with AddInstance as only we can guarantee an atomic
1334
    rename.
1335

1336
    """
1337
    if old_name not in self._config_data.instances:
1338
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1339
    inst = self._config_data.instances[old_name]
1340
    del self._config_data.instances[old_name]
1341
    inst.name = new_name
1342

    
1343
    for disk in inst.disks:
1344
      if disk.dev_type == constants.LD_FILE:
1345
        # rename the file paths in logical and physical id
1346
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1347
        disk_fname = "disk%s" % disk.iv_name.split("/")[1]
1348
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
1349
                                              utils.PathJoin(file_storage_dir,
1350
                                                             inst.name,
1351
                                                             disk_fname))
1352

    
1353
    # Force update of ssconf files
1354
    self._config_data.cluster.serial_no += 1
1355

    
1356
    self._config_data.instances[inst.name] = inst
1357
    self._WriteConfig()
1358

    
1359
  @locking.ssynchronized(_config_lock)
1360
  def MarkInstanceDown(self, instance_name):
1361
    """Mark the status of an instance to down in the configuration.
1362

1363
    """
1364
    self._SetInstanceStatus(instance_name, False)
1365

    
1366
  def _UnlockedGetInstanceList(self):
1367
    """Get the list of instances.
1368

1369
    This function is for internal use, when the config lock is already held.
1370

1371
    """
1372
    return self._config_data.instances.keys()
1373

    
1374
  @locking.ssynchronized(_config_lock, shared=1)
1375
  def GetInstanceList(self):
1376
    """Get the list of instances.
1377

1378
    @return: array of instances, ex. ['instance2.example.com',
1379
        'instance1.example.com']
1380

1381
    """
1382
    return self._UnlockedGetInstanceList()
1383

    
1384
  @locking.ssynchronized(_config_lock, shared=1)
1385
  def ExpandInstanceName(self, short_name):
1386
    """Attempt to expand an incomplete instance name.
1387

1388
    """
1389
    return utils.MatchNameComponent(short_name,
1390
                                    self._config_data.instances.keys(),
1391
                                    case_sensitive=False)
1392

    
1393
  def _UnlockedGetInstanceInfo(self, instance_name):
1394
    """Returns information about an instance.
1395

1396
    This function is for internal use, when the config lock is already held.
1397

1398
    """
1399
    if instance_name not in self._config_data.instances:
1400
      return None
1401

    
1402
    return self._config_data.instances[instance_name]
1403

    
1404
  @locking.ssynchronized(_config_lock, shared=1)
1405
  def GetInstanceInfo(self, instance_name):
1406
    """Returns information about an instance.
1407

1408
    It takes the information from the configuration file. Other information of
1409
    an instance are taken from the live systems.
1410

1411
    @param instance_name: name of the instance, e.g.
1412
        I{instance1.example.com}
1413

1414
    @rtype: L{objects.Instance}
1415
    @return: the instance object
1416

1417
    """
1418
    return self._UnlockedGetInstanceInfo(instance_name)
1419

    
1420
  @locking.ssynchronized(_config_lock, shared=1)
1421
  def GetAllInstancesInfo(self):
1422
    """Get the configuration of all instances.
1423

1424
    @rtype: dict
1425
    @return: dict of (instance, instance_info), where instance_info is what
1426
              would GetInstanceInfo return for the node
1427

1428
    """
1429
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1430
                    for instance in self._UnlockedGetInstanceList()])
1431
    return my_dict
1432

    
1433
  @locking.ssynchronized(_config_lock)
1434
  def AddNode(self, node, ec_id):
1435
    """Add a node to the configuration.
1436

1437
    @type node: L{objects.Node}
1438
    @param node: a Node instance
1439

1440
    """
1441
    logging.info("Adding node %s to configuration", node.name)
1442

    
1443
    self._EnsureUUID(node, ec_id)
1444

    
1445
    node.serial_no = 1
1446
    node.ctime = node.mtime = time.time()
1447
    self._UnlockedAddNodeToGroup(node.name, node.group)
1448
    self._config_data.nodes[node.name] = node
1449
    self._config_data.cluster.serial_no += 1
1450
    self._WriteConfig()
1451

    
1452
  @locking.ssynchronized(_config_lock)
1453
  def RemoveNode(self, node_name):
1454
    """Remove a node from the configuration.
1455

1456
    """
1457
    logging.info("Removing node %s from configuration", node_name)
1458

    
1459
    if node_name not in self._config_data.nodes:
1460
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1461

    
1462
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1463
    del self._config_data.nodes[node_name]
1464
    self._config_data.cluster.serial_no += 1
1465
    self._WriteConfig()
1466

    
1467
  @locking.ssynchronized(_config_lock, shared=1)
1468
  def ExpandNodeName(self, short_name):
1469
    """Attempt to expand an incomplete instance name.
1470

1471
    """
1472
    return utils.MatchNameComponent(short_name,
1473
                                    self._config_data.nodes.keys(),
1474
                                    case_sensitive=False)
1475

    
1476
  def _UnlockedGetNodeInfo(self, node_name):
1477
    """Get the configuration of a node, as stored in the config.
1478

1479
    This function is for internal use, when the config lock is already
1480
    held.
1481

1482
    @param node_name: the node name, e.g. I{node1.example.com}
1483

1484
    @rtype: L{objects.Node}
1485
    @return: the node object
1486

1487
    """
1488
    if node_name not in self._config_data.nodes:
1489
      return None
1490

    
1491
    return self._config_data.nodes[node_name]
1492

    
1493
  @locking.ssynchronized(_config_lock, shared=1)
1494
  def GetNodeInfo(self, node_name):
1495
    """Get the configuration of a node, as stored in the config.
1496

1497
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1498

1499
    @param node_name: the node name, e.g. I{node1.example.com}
1500

1501
    @rtype: L{objects.Node}
1502
    @return: the node object
1503

1504
    """
1505
    return self._UnlockedGetNodeInfo(node_name)
1506

    
1507
  @locking.ssynchronized(_config_lock, shared=1)
1508
  def GetNodeInstances(self, node_name):
1509
    """Get the instances of a node, as stored in the config.
1510

1511
    @param node_name: the node name, e.g. I{node1.example.com}
1512

1513
    @rtype: (list, list)
1514
    @return: a tuple with two lists: the primary and the secondary instances
1515

1516
    """
1517
    pri = []
1518
    sec = []
1519
    for inst in self._config_data.instances.values():
1520
      if inst.primary_node == node_name:
1521
        pri.append(inst.name)
1522
      if node_name in inst.secondary_nodes:
1523
        sec.append(inst.name)
1524
    return (pri, sec)
1525

    
1526
  def _UnlockedGetNodeList(self):
1527
    """Return the list of nodes which are in the configuration.
1528

1529
    This function is for internal use, when the config lock is already
1530
    held.
1531

1532
    @rtype: list
1533

1534
    """
1535
    return self._config_data.nodes.keys()
1536

    
1537
  @locking.ssynchronized(_config_lock, shared=1)
1538
  def GetNodeList(self):
1539
    """Return the list of nodes which are in the configuration.
1540

1541
    """
1542
    return self._UnlockedGetNodeList()
1543

    
1544
  def _UnlockedGetOnlineNodeList(self):
1545
    """Return the list of nodes which are online.
1546

1547
    """
1548
    all_nodes = [self._UnlockedGetNodeInfo(node)
1549
                 for node in self._UnlockedGetNodeList()]
1550
    return [node.name for node in all_nodes if not node.offline]
1551

    
1552
  @locking.ssynchronized(_config_lock, shared=1)
1553
  def GetOnlineNodeList(self):
1554
    """Return the list of nodes which are online.
1555

1556
    """
1557
    return self._UnlockedGetOnlineNodeList()
1558

    
1559
  @locking.ssynchronized(_config_lock, shared=1)
1560
  def GetVmCapableNodeList(self):
1561
    """Return the list of nodes which are not vm capable.
1562

1563
    """
1564
    all_nodes = [self._UnlockedGetNodeInfo(node)
1565
                 for node in self._UnlockedGetNodeList()]
1566
    return [node.name for node in all_nodes if node.vm_capable]
1567

    
1568
  @locking.ssynchronized(_config_lock, shared=1)
1569
  def GetNonVmCapableNodeList(self):
1570
    """Return the list of nodes which are not vm capable.
1571

1572
    """
1573
    all_nodes = [self._UnlockedGetNodeInfo(node)
1574
                 for node in self._UnlockedGetNodeList()]
1575
    return [node.name for node in all_nodes if not node.vm_capable]
1576

    
1577
  @locking.ssynchronized(_config_lock, shared=1)
1578
  def GetAllNodesInfo(self):
1579
    """Get the configuration of all nodes.
1580

1581
    @rtype: dict
1582
    @return: dict of (node, node_info), where node_info is what
1583
              would GetNodeInfo return for the node
1584

1585
    """
1586
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
1587
                    for node in self._UnlockedGetNodeList()])
1588
    return my_dict
1589

    
1590
  @locking.ssynchronized(_config_lock, shared=1)
1591
  def GetNodeGroupsFromNodes(self, nodes):
1592
    """Returns groups for a list of nodes.
1593

1594
    @type nodes: list of string
1595
    @param nodes: List of node names
1596
    @rtype: frozenset
1597

1598
    """
1599
    return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1600

    
1601
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1602
    """Get the number of current and maximum desired and possible candidates.
1603

1604
    @type exceptions: list
1605
    @param exceptions: if passed, list of nodes that should be ignored
1606
    @rtype: tuple
1607
    @return: tuple of (current, desired and possible, possible)
1608

1609
    """
1610
    mc_now = mc_should = mc_max = 0
1611
    for node in self._config_data.nodes.values():
1612
      if exceptions and node.name in exceptions:
1613
        continue
1614
      if not (node.offline or node.drained) and node.master_capable:
1615
        mc_max += 1
1616
      if node.master_candidate:
1617
        mc_now += 1
1618
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1619
    return (mc_now, mc_should, mc_max)
1620

    
1621
  @locking.ssynchronized(_config_lock, shared=1)
1622
  def GetMasterCandidateStats(self, exceptions=None):
1623
    """Get the number of current and maximum possible candidates.
1624

1625
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1626

1627
    @type exceptions: list
1628
    @param exceptions: if passed, list of nodes that should be ignored
1629
    @rtype: tuple
1630
    @return: tuple of (current, max)
1631

1632
    """
1633
    return self._UnlockedGetMasterCandidateStats(exceptions)
1634

    
1635
  @locking.ssynchronized(_config_lock)
1636
  def MaintainCandidatePool(self, exceptions):
1637
    """Try to grow the candidate pool to the desired size.
1638

1639
    @type exceptions: list
1640
    @param exceptions: if passed, list of nodes that should be ignored
1641
    @rtype: list
1642
    @return: list with the adjusted nodes (L{objects.Node} instances)
1643

1644
    """
1645
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1646
    mod_list = []
1647
    if mc_now < mc_max:
1648
      node_list = self._config_data.nodes.keys()
1649
      random.shuffle(node_list)
1650
      for name in node_list:
1651
        if mc_now >= mc_max:
1652
          break
1653
        node = self._config_data.nodes[name]
1654
        if (node.master_candidate or node.offline or node.drained or
1655
            node.name in exceptions or not node.master_capable):
1656
          continue
1657
        mod_list.append(node)
1658
        node.master_candidate = True
1659
        node.serial_no += 1
1660
        mc_now += 1
1661
      if mc_now != mc_max:
1662
        # this should not happen
1663
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1664
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1665
      if mod_list:
1666
        self._config_data.cluster.serial_no += 1
1667
        self._WriteConfig()
1668

    
1669
    return mod_list
1670

    
1671
  def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1672
    """Add a given node to the specified group.
1673

1674
    """
1675
    if nodegroup_uuid not in self._config_data.nodegroups:
1676
      # This can happen if a node group gets deleted between its lookup and
1677
      # when we're adding the first node to it, since we don't keep a lock in
1678
      # the meantime. It's ok though, as we'll fail cleanly if the node group
1679
      # is not found anymore.
1680
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1681
    if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1682
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1683

    
1684
  def _UnlockedRemoveNodeFromGroup(self, node):
1685
    """Remove a given node from its group.
1686

1687
    """
1688
    nodegroup = node.group
1689
    if nodegroup not in self._config_data.nodegroups:
1690
      logging.warning("Warning: node '%s' has unknown node group '%s'"
1691
                      " (while being removed from it)", node.name, nodegroup)
1692
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
1693
    if node.name not in nodegroup_obj.members:
1694
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
1695
                      " (while being removed from it)", node.name, nodegroup)
1696
    else:
1697
      nodegroup_obj.members.remove(node.name)
1698

    
1699
  def _BumpSerialNo(self):
1700
    """Bump up the serial number of the config.
1701

1702
    """
1703
    self._config_data.serial_no += 1
1704
    self._config_data.mtime = time.time()
1705

    
1706
  def _AllUUIDObjects(self):
1707
    """Returns all objects with uuid attributes.
1708

1709
    """
1710
    return (self._config_data.instances.values() +
1711
            self._config_data.nodes.values() +
1712
            self._config_data.nodegroups.values() +
1713
            [self._config_data.cluster])
1714

    
1715
  def _OpenConfig(self, accept_foreign):
1716
    """Read the config data from disk.
1717

1718
    """
1719
    raw_data = utils.ReadFile(self._cfg_file)
1720

    
1721
    try:
1722
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1723
    except Exception, err:
1724
      raise errors.ConfigurationError(err)
1725

    
1726
    # Make sure the configuration has the right version
1727
    _ValidateConfig(data)
1728

    
1729
    if (not hasattr(data, 'cluster') or
1730
        not hasattr(data.cluster, 'rsahostkeypub')):
1731
      raise errors.ConfigurationError("Incomplete configuration"
1732
                                      " (missing cluster.rsahostkeypub)")
1733

    
1734
    if data.cluster.master_node != self._my_hostname and not accept_foreign:
1735
      msg = ("The configuration denotes node %s as master, while my"
1736
             " hostname is %s; opening a foreign configuration is only"
1737
             " possible in accept_foreign mode" %
1738
             (data.cluster.master_node, self._my_hostname))
1739
      raise errors.ConfigurationError(msg)
1740

    
1741
    # Upgrade configuration if needed
1742
    data.UpgradeConfig()
1743

    
1744
    self._config_data = data
1745
    # reset the last serial as -1 so that the next write will cause
1746
    # ssconf update
1747
    self._last_cluster_serial = -1
1748

    
1749
    # And finally run our (custom) config upgrade sequence
1750
    self._UpgradeConfig()
1751

    
1752
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
1753

    
1754
  def _UpgradeConfig(self):
1755
    """Run upgrade steps that cannot be done purely in the objects.
1756

1757
    This is because some data elements need uniqueness across the
1758
    whole configuration, etc.
1759

1760
    @warning: this function will call L{_WriteConfig()}, but also
1761
        L{DropECReservations} so it needs to be called only from a
1762
        "safe" place (the constructor). If one wanted to call it with
1763
        the lock held, a DropECReservationUnlocked would need to be
1764
        created first, to avoid causing deadlock.
1765

1766
    """
1767
    modified = False
1768
    for item in self._AllUUIDObjects():
1769
      if item.uuid is None:
1770
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
1771
        modified = True
1772
    if not self._config_data.nodegroups:
1773
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
1774
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
1775
                                            members=[])
1776
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
1777
      modified = True
1778
    for node in self._config_data.nodes.values():
1779
      if not node.group:
1780
        node.group = self.LookupNodeGroup(None)
1781
        modified = True
1782
      # This is technically *not* an upgrade, but needs to be done both when
1783
      # nodegroups are being added, and upon normally loading the config,
1784
      # because the members list of a node group is discarded upon
1785
      # serializing/deserializing the object.
1786
      self._UnlockedAddNodeToGroup(node.name, node.group)
1787
    if modified:
1788
      self._WriteConfig()
1789
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
1790
      # only called at config init time, without the lock held
1791
      self.DropECReservations(_UPGRADE_CONFIG_JID)
1792

    
1793
  def _DistributeConfig(self, feedback_fn):
1794
    """Distribute the configuration to the other nodes.
1795

1796
    Currently, this only copies the configuration file. In the future,
1797
    it could be used to encapsulate the 2/3-phase update mechanism.
1798

1799
    """
1800
    if self._offline:
1801
      return True
1802

    
1803
    bad = False
1804

    
1805
    node_list = []
1806
    addr_list = []
1807
    myhostname = self._my_hostname
1808
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1809
    # since the node list comes from _UnlocketGetNodeList, and we are
1810
    # called with the lock held, so no modifications should take place
1811
    # in between
1812
    for node_name in self._UnlockedGetNodeList():
1813
      if node_name == myhostname:
1814
        continue
1815
      node_info = self._UnlockedGetNodeInfo(node_name)
1816
      if not node_info.master_candidate:
1817
        continue
1818
      node_list.append(node_info.name)
1819
      addr_list.append(node_info.primary_ip)
1820

    
1821
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1822
                                            address_list=addr_list)
1823
    for to_node, to_result in result.items():
1824
      msg = to_result.fail_msg
1825
      if msg:
1826
        msg = ("Copy of file %s to node %s failed: %s" %
1827
               (self._cfg_file, to_node, msg))
1828
        logging.error(msg)
1829

    
1830
        if feedback_fn:
1831
          feedback_fn(msg)
1832

    
1833
        bad = True
1834

    
1835
    return not bad
1836

    
1837
  def _WriteConfig(self, destination=None, feedback_fn=None):
1838
    """Write the configuration data to persistent storage.
1839

1840
    """
1841
    assert feedback_fn is None or callable(feedback_fn)
1842

    
1843
    # Warn on config errors, but don't abort the save - the
1844
    # configuration has already been modified, and we can't revert;
1845
    # the best we can do is to warn the user and save as is, leaving
1846
    # recovery to the user
1847
    config_errors = self._UnlockedVerifyConfig()
1848
    if config_errors:
1849
      errmsg = ("Configuration data is not consistent: %s" %
1850
                (utils.CommaJoin(config_errors)))
1851
      logging.critical(errmsg)
1852
      if feedback_fn:
1853
        feedback_fn(errmsg)
1854

    
1855
    if destination is None:
1856
      destination = self._cfg_file
1857
    self._BumpSerialNo()
1858
    txt = serializer.Dump(self._config_data.ToDict())
1859

    
1860
    getents = self._getents()
1861
    try:
1862
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
1863
                               close=False, gid=getents.confd_gid, mode=0640)
1864
    except errors.LockError:
1865
      raise errors.ConfigurationError("The configuration file has been"
1866
                                      " modified since the last write, cannot"
1867
                                      " update")
1868
    try:
1869
      self._cfg_id = utils.GetFileID(fd=fd)
1870
    finally:
1871
      os.close(fd)
1872

    
1873
    self.write_count += 1
1874

    
1875
    # and redistribute the config file to master candidates
1876
    self._DistributeConfig(feedback_fn)
1877

    
1878
    # Write ssconf files on all nodes (including locally)
1879
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1880
      if not self._offline:
1881
        result = rpc.RpcRunner.call_write_ssconf_files(
1882
          self._UnlockedGetOnlineNodeList(),
1883
          self._UnlockedGetSsconfValues())
1884

    
1885
        for nname, nresu in result.items():
1886
          msg = nresu.fail_msg
1887
          if msg:
1888
            errmsg = ("Error while uploading ssconf files to"
1889
                      " node %s: %s" % (nname, msg))
1890
            logging.warning(errmsg)
1891

    
1892
            if feedback_fn:
1893
              feedback_fn(errmsg)
1894

    
1895
      self._last_cluster_serial = self._config_data.cluster.serial_no
1896

    
1897
  def _UnlockedGetSsconfValues(self):
1898
    """Return the values needed by ssconf.
1899

1900
    @rtype: dict
1901
    @return: a dictionary with keys the ssconf names and values their
1902
        associated value
1903

1904
    """
1905
    fn = "\n".join
1906
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1907
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1908
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1909
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
1910
                    for ninfo in node_info]
1911
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
1912
                    for ninfo in node_info]
1913

    
1914
    instance_data = fn(instance_names)
1915
    off_data = fn(node.name for node in node_info if node.offline)
1916
    on_data = fn(node.name for node in node_info if not node.offline)
1917
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1918
    mc_ips_data = fn(node.primary_ip for node in node_info
1919
                     if node.master_candidate)
1920
    node_data = fn(node_names)
1921
    node_pri_ips_data = fn(node_pri_ips)
1922
    node_snd_ips_data = fn(node_snd_ips)
1923

    
1924
    cluster = self._config_data.cluster
1925
    cluster_tags = fn(cluster.GetTags())
1926

    
1927
    hypervisor_list = fn(cluster.enabled_hypervisors)
1928

    
1929
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
1930

    
1931
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
1932
                  self._config_data.nodegroups.values()]
1933
    nodegroups_data = fn(utils.NiceSort(nodegroups))
1934
    networks = ["%s %s" % (net.uuid, net.name) for net in
1935
                self._config_data.networks.values()]
1936
    networks_data = fn(utils.NiceSort(networks))
1937

    
1938
    ssconf_values = {
1939
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1940
      constants.SS_CLUSTER_TAGS: cluster_tags,
1941
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1942
      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
1943
      constants.SS_MASTER_CANDIDATES: mc_data,
1944
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
1945
      constants.SS_MASTER_IP: cluster.master_ip,
1946
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1947
      constants.SS_MASTER_NODE: cluster.master_node,
1948
      constants.SS_NODE_LIST: node_data,
1949
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
1950
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
1951
      constants.SS_OFFLINE_NODES: off_data,
1952
      constants.SS_ONLINE_NODES: on_data,
1953
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
1954
      constants.SS_INSTANCE_LIST: instance_data,
1955
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1956
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
1957
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
1958
      constants.SS_UID_POOL: uid_pool,
1959
      constants.SS_NODEGROUPS: nodegroups_data,
1960
      constants.SS_NETWORKS: networks_data,
1961
      }
1962
    bad_values = [(k, v) for k, v in ssconf_values.items()
1963
                  if not isinstance(v, (str, basestring))]
1964
    if bad_values:
1965
      err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
1966
      raise errors.ConfigurationError("Some ssconf key(s) have non-string"
1967
                                      " values: %s" % err)
1968
    return ssconf_values
1969

    
1970
  @locking.ssynchronized(_config_lock, shared=1)
1971
  def GetSsconfValues(self):
1972
    """Wrapper using lock around _UnlockedGetSsconf().
1973

1974
    """
1975
    return self._UnlockedGetSsconfValues()
1976

    
1977
  @locking.ssynchronized(_config_lock, shared=1)
1978
  def GetVGName(self):
1979
    """Return the volume group name.
1980

1981
    """
1982
    return self._config_data.cluster.volume_group_name
1983

    
1984
  @locking.ssynchronized(_config_lock)
1985
  def SetVGName(self, vg_name):
1986
    """Set the volume group name.
1987

1988
    """
1989
    self._config_data.cluster.volume_group_name = vg_name
1990
    self._config_data.cluster.serial_no += 1
1991
    self._WriteConfig()
1992

    
1993
  @locking.ssynchronized(_config_lock, shared=1)
1994
  def GetDRBDHelper(self):
1995
    """Return DRBD usermode helper.
1996

1997
    """
1998
    return self._config_data.cluster.drbd_usermode_helper
1999

    
2000
  @locking.ssynchronized(_config_lock)
2001
  def SetDRBDHelper(self, drbd_helper):
2002
    """Set DRBD usermode helper.
2003

2004
    """
2005
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2006
    self._config_data.cluster.serial_no += 1
2007
    self._WriteConfig()
2008

    
2009
  @locking.ssynchronized(_config_lock, shared=1)
2010
  def GetMACPrefix(self):
2011
    """Return the mac prefix.
2012

2013
    """
2014
    return self._config_data.cluster.mac_prefix
2015

    
2016
  @locking.ssynchronized(_config_lock, shared=1)
2017
  def GetClusterInfo(self):
2018
    """Returns information about the cluster
2019

2020
    @rtype: L{objects.Cluster}
2021
    @return: the cluster object
2022

2023
    """
2024
    return self._config_data.cluster
2025

    
2026
  @locking.ssynchronized(_config_lock, shared=1)
2027
  def HasAnyDiskOfType(self, dev_type):
2028
    """Check if in there is at disk of the given type in the configuration.
2029

2030
    """
2031
    return self._config_data.HasAnyDiskOfType(dev_type)
2032

    
2033
  @locking.ssynchronized(_config_lock)
2034
  def Update(self, target, feedback_fn, ec_id=None):
2035
    """Notify function to be called after updates.
2036

2037
    This function must be called when an object (as returned by
2038
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2039
    caller wants the modifications saved to the backing store. Note
2040
    that all modified objects will be saved, but the target argument
2041
    is the one the caller wants to ensure that it's saved.
2042

2043
    @param target: an instance of either L{objects.Cluster},
2044
        L{objects.Node} or L{objects.Instance} which is existing in
2045
        the cluster
2046
    @param feedback_fn: Callable feedback function
2047

2048
    """
2049
    if self._config_data is None:
2050
      raise errors.ProgrammerError("Configuration file not read,"
2051
                                   " cannot save.")
2052
    update_serial = False
2053
    if isinstance(target, objects.Cluster):
2054
      test = target == self._config_data.cluster
2055
    elif isinstance(target, objects.Node):
2056
      test = target in self._config_data.nodes.values()
2057
      update_serial = True
2058
    elif isinstance(target, objects.Instance):
2059
      test = target in self._config_data.instances.values()
2060
    elif isinstance(target, objects.NodeGroup):
2061
      test = target in self._config_data.nodegroups.values()
2062
    elif isinstance(target, objects.Network):
2063
      test = target in self._config_data.networks.values()
2064
    else:
2065
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2066
                                   " ConfigWriter.Update" % type(target))
2067
    if not test:
2068
      raise errors.ConfigurationError("Configuration updated since object"
2069
                                      " has been read or unknown object")
2070
    target.serial_no += 1
2071
    target.mtime = now = time.time()
2072

    
2073
    if update_serial:
2074
      # for node updates, we need to increase the cluster serial too
2075
      self._config_data.cluster.serial_no += 1
2076
      self._config_data.cluster.mtime = now
2077

    
2078
    if isinstance(target, objects.Instance):
2079
      self._UnlockedReleaseDRBDMinors(target.name)
2080
      if ec_id is not None:
2081
        # Commit all reserved IPs
2082
        self._UnlockedCommitReservedIps(ec_id)
2083
        # Drop the IP reservations so that we can call Update() multiple times
2084
        # from within the same LU
2085
        self._temporary_ips.DropECReservations(ec_id)
2086

    
2087
    self._WriteConfig(feedback_fn=feedback_fn)
2088

    
2089
  @locking.ssynchronized(_config_lock)
2090
  def DropECReservations(self, ec_id):
2091
    """Drop per-execution-context reservations
2092

2093
    """
2094
    for rm in self._all_rms:
2095
      rm.DropECReservations(ec_id)
2096

    
2097
  @locking.ssynchronized(_config_lock, shared=1)
2098
  def GetAllNetworksInfo(self):
2099
    """Get the configuration of all networks
2100

2101
    """
2102
    return dict(self._config_data.networks)
2103

    
2104
  def _UnlockedGetNetworkList(self):
2105
    """Get the list of networks.
2106

2107
    This function is for internal use, when the config lock is already held.
2108

2109
    """
2110
    return self._config_data.networks.keys()
2111

    
2112
  @locking.ssynchronized(_config_lock, shared=1)
2113
  def GetNetworkList(self):
2114
    """Get the list of networks.
2115

2116
    @return: array of networks, ex. ["main", "vlan100", "200]
2117

2118
    """
2119
    return self._UnlockedGetNetworkList()
2120

    
2121
  def _UnlockedGetNetwork(self, uuid):
2122
    """Returns information about a network.
2123

2124
    This function is for internal use, when the config lock is already held.
2125

2126
    """
2127
    if uuid not in self._config_data.networks:
2128
      return None
2129

    
2130
    return self._config_data.networks[uuid]
2131

    
2132
  @locking.ssynchronized(_config_lock, shared=1)
2133
  def GetNetwork(self, uuid):
2134
    """Returns information about a network.
2135

2136
    It takes the information from the configuration file.
2137

2138
    @param uuid: UUID of the network
2139

2140
    @rtype: L{objects.Network}
2141
    @return: the network object
2142

2143
    """
2144
    return self._UnlockedGetNetwork(uuid)
2145

    
2146
  @locking.ssynchronized(_config_lock)
2147
  def AddNetwork(self, net, ec_id):
2148
    """Add a network to the configuration.
2149

2150
    @type net: L{objects.Network}
2151
    @param net: the Network object to add
2152
    @type ec_id: string
2153
    @param ec_id: unique id for the job to use when creating a missing UUID
2154

2155
    """
2156
    self._UnlockedAddNetwork(net, ec_id)
2157
    self._WriteConfig()
2158

    
2159
  def _UnlockedAddNetwork(self, net, ec_id):
2160
    """Add a network to the configuration.
2161

2162
    """
2163
    logging.info("Adding network %s to configuration", net.name)
2164

    
2165
    self._EnsureUUID(net, ec_id)
2166

    
2167
    try:
2168
      existing_uuid = self._UnlockedLookupNetwork(net.name)
2169
    except errors.OpPrereqError:
2170
      pass
2171
    else:
2172
      raise errors.OpPrereqError("Desired network name '%s' already exists as a"
2173
                                 " network (UUID: %s)" %
2174
                                 (net.name, existing_uuid),
2175
                                 errors.ECODE_EXISTS)
2176

    
2177
    self._config_data.networks[net.uuid] = net
2178
    self._config_data.cluster.serial_no += 1
2179

    
2180
  def _UnlockedLookupNetwork(self, target):
2181
    """Lookup a network's UUID.
2182

2183
    @type target: string
2184
    @param target: network name or UUID
2185
    @rtype: string
2186
    @return: network UUID
2187
    @raises errors.OpPrereqError: when the target network cannot be found
2188

2189
    """
2190
    if target in self._config_data.networks:
2191
      return target
2192
    for net in self._config_data.networks.values():
2193
      if net.name == target:
2194
        return net.uuid
2195
    raise errors.OpPrereqError("Network '%s' not found" % target,
2196
                               errors.ECODE_NOENT)
2197

    
2198
  @locking.ssynchronized(_config_lock, shared=1)
2199
  def LookupNetwork(self, target):
2200
    """Lookup a network's UUID.
2201

2202
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2203

2204
    @type target: string
2205
    @param target: network name or UUID
2206
    @rtype: string
2207
    @return: network UUID
2208

2209
    """
2210
    return self._UnlockedLookupNetwork(target)