Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ a8b9a6e3

History | View | Annotate | Download (96.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 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
import ganeti.rpc.node as rpc
49
import ganeti.wconfd as wc
50
from ganeti import objects
51
from ganeti import serializer
52
from ganeti import uidpool
53
from ganeti import netutils
54
from ganeti import runtime
55
from ganeti import pathutils
56
from ganeti import network
57

    
58

    
59
_config_lock = locking.SharedLock("ConfigWriter")
60

    
61
# job id used for resource management at config upgrade time
62
_UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
63

    
64

    
65
def _ValidateConfig(data):
66
  """Verifies that a configuration objects looks valid.
67

68
  This only verifies the version of the configuration.
69

70
  @raise errors.ConfigurationError: if the version differs from what
71
      we expect
72

73
  """
74
  if data.version != constants.CONFIG_VERSION:
75
    raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
76

    
77

    
78
class TemporaryReservationManager:
79
  """A temporary resource reservation manager.
80

81
  This is used to reserve resources in a job, before using them, making sure
82
  other jobs cannot get them in the meantime.
83

84
  """
85
  def __init__(self):
86
    self._ec_reserved = {}
87

    
88
  def Reserved(self, resource):
89
    for holder_reserved in self._ec_reserved.values():
90
      if resource in holder_reserved:
91
        return True
92
    return False
93

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

    
103
  def DropECReservations(self, ec_id):
104
    if ec_id in self._ec_reserved:
105
      del self._ec_reserved[ec_id]
106

    
107
  def GetReserved(self):
108
    all_reserved = set()
109
    for holder_reserved in self._ec_reserved.values():
110
      all_reserved.update(holder_reserved)
111
    return all_reserved
112

    
113
  def GetECReserved(self, ec_id):
114
    """ Used when you want to retrieve all reservations for a specific
115
        execution context. E.g when commiting reserved IPs for a specific
116
        network.
117

118
    """
119
    ec_reserved = set()
120
    if ec_id in self._ec_reserved:
121
      ec_reserved.update(self._ec_reserved[ec_id])
122
    return ec_reserved
123

    
124
  def Generate(self, existing, generate_one_fn, ec_id):
125
    """Generate a new resource of this type
126

127
    """
128
    assert callable(generate_one_fn)
129

    
130
    all_elems = self.GetReserved()
131
    all_elems.update(existing)
132
    retries = 64
133
    while retries > 0:
134
      new_resource = generate_one_fn()
135
      if new_resource is not None and new_resource not in all_elems:
136
        break
137
    else:
138
      raise errors.ConfigurationError("Not able generate new resource"
139
                                      " (last tried: %s)" % new_resource)
140
    self.Reserve(ec_id, new_resource)
141
    return new_resource
142

    
143

    
144
def _MatchNameComponentIgnoreCase(short_name, names):
145
  """Wrapper around L{utils.text.MatchNameComponent}.
146

147
  """
148
  return utils.MatchNameComponent(short_name, names, case_sensitive=False)
149

    
150

    
151
def _CheckInstanceDiskIvNames(disks):
152
  """Checks if instance's disks' C{iv_name} attributes are in order.
153

154
  @type disks: list of L{objects.Disk}
155
  @param disks: List of disks
156
  @rtype: list of tuples; (int, string, string)
157
  @return: List of wrongly named disks, each tuple contains disk index,
158
    expected and actual name
159

160
  """
161
  result = []
162

    
163
  for (idx, disk) in enumerate(disks):
164
    exp_iv_name = "disk/%s" % idx
165
    if disk.iv_name != exp_iv_name:
166
      result.append((idx, exp_iv_name, disk.iv_name))
167

    
168
  return result
169

    
170

    
171
class ConfigWriter(object):
172
  """The interface to the cluster configuration.
173

174
  @ivar _temporary_lvs: reservation manager for temporary LVs
175
  @ivar _all_rms: a list of all temporary reservation managers
176

177
  """
178
  def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
179
               accept_foreign=False):
180
    self.write_count = 0
181
    self._lock = _config_lock
182
    self._config_data = None
183
    self._offline = offline
184
    if cfg_file is None:
185
      self._cfg_file = pathutils.CLUSTER_CONF_FILE
186
    else:
187
      self._cfg_file = cfg_file
188
    self._getents = _getents
189
    self._temporary_ids = TemporaryReservationManager()
190
    self._temporary_drbds = {}
191
    self._temporary_macs = TemporaryReservationManager()
192
    self._temporary_secrets = TemporaryReservationManager()
193
    self._temporary_lvs = TemporaryReservationManager()
194
    self._temporary_ips = TemporaryReservationManager()
195
    self._all_rms = [self._temporary_ids, self._temporary_macs,
196
                     self._temporary_secrets, self._temporary_lvs,
197
                     self._temporary_ips]
198
    # Note: in order to prevent errors when resolving our name later,
199
    # we compute it here once and reuse it; it's
200
    # better to raise an error before starting to modify the config
201
    # file than after it was modified
202
    self._my_hostname = netutils.Hostname.GetSysName()
203
    self._cfg_id = None
204
    self._context = None
205
    self._wconfd = None
206
    self._OpenConfig(accept_foreign)
207

    
208
  def _GetRpc(self, address_list):
209
    """Returns RPC runner for configuration.
210

211
    """
212
    return rpc.ConfigRunner(self._context, address_list)
213

    
214
  def SetContext(self, context):
215
    """Sets Ganeti context.
216

217
    """
218
    self._context = context
219

    
220
  # this method needs to be static, so that we can call it on the class
221
  @staticmethod
222
  def IsCluster():
223
    """Check if the cluster is configured.
224

225
    """
226
    return os.path.exists(pathutils.CLUSTER_CONF_FILE)
227

    
228
  @locking.ssynchronized(_config_lock, shared=1)
229
  def GetNdParams(self, node):
230
    """Get the node params populated with cluster defaults.
231

232
    @type node: L{objects.Node}
233
    @param node: The node we want to know the params for
234
    @return: A dict with the filled in node params
235

236
    """
237
    nodegroup = self._UnlockedGetNodeGroup(node.group)
238
    return self._config_data.cluster.FillND(node, nodegroup)
239

    
240
  @locking.ssynchronized(_config_lock, shared=1)
241
  def GetNdGroupParams(self, nodegroup):
242
    """Get the node groups params populated with cluster defaults.
243

244
    @type nodegroup: L{objects.NodeGroup}
245
    @param nodegroup: The node group we want to know the params for
246
    @return: A dict with the filled in node group params
247

248
    """
249
    return self._config_data.cluster.FillNDGroup(nodegroup)
250

    
251
  @locking.ssynchronized(_config_lock, shared=1)
252
  def GetInstanceDiskParams(self, instance):
253
    """Get the disk params populated with inherit chain.
254

255
    @type instance: L{objects.Instance}
256
    @param instance: The instance we want to know the params for
257
    @return: A dict with the filled in disk params
258

259
    """
260
    node = self._UnlockedGetNodeInfo(instance.primary_node)
261
    nodegroup = self._UnlockedGetNodeGroup(node.group)
262
    return self._UnlockedGetGroupDiskParams(nodegroup)
263

    
264
  @locking.ssynchronized(_config_lock, shared=1)
265
  def GetGroupDiskParams(self, group):
266
    """Get the disk params populated with inherit chain.
267

268
    @type group: L{objects.NodeGroup}
269
    @param group: The group we want to know the params for
270
    @return: A dict with the filled in disk params
271

272
    """
273
    return self._UnlockedGetGroupDiskParams(group)
274

    
275
  def _UnlockedGetGroupDiskParams(self, group):
276
    """Get the disk params populated with inherit chain down to node-group.
277

278
    @type group: L{objects.NodeGroup}
279
    @param group: The group we want to know the params for
280
    @return: A dict with the filled in disk params
281

282
    """
283
    return self._config_data.cluster.SimpleFillDP(group.diskparams)
284

    
285
  def _UnlockedGetNetworkMACPrefix(self, net_uuid):
286
    """Return the network mac prefix if it exists or the cluster level default.
287

288
    """
289
    prefix = None
290
    if net_uuid:
291
      nobj = self._UnlockedGetNetwork(net_uuid)
292
      if nobj.mac_prefix:
293
        prefix = nobj.mac_prefix
294

    
295
    return prefix
296

    
297
  def _GenerateOneMAC(self, prefix=None):
298
    """Return a function that randomly generates a MAC suffic
299
       and appends it to the given prefix. If prefix is not given get
300
       the cluster level default.
301

302
    """
303
    if not prefix:
304
      prefix = self._config_data.cluster.mac_prefix
305

    
306
    def GenMac():
307
      byte1 = random.randrange(0, 256)
308
      byte2 = random.randrange(0, 256)
309
      byte3 = random.randrange(0, 256)
310
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
311
      return mac
312

    
313
    return GenMac
314

    
315
  @locking.ssynchronized(_config_lock, shared=1)
316
  def GenerateMAC(self, net_uuid, ec_id):
317
    """Generate a MAC for an instance.
318

319
    This should check the current instances for duplicates.
320

321
    """
322
    existing = self._AllMACs()
323
    prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
324
    gen_mac = self._GenerateOneMAC(prefix)
325
    return self._temporary_ids.Generate(existing, gen_mac, ec_id)
326

    
327
  @locking.ssynchronized(_config_lock, shared=1)
328
  def ReserveMAC(self, mac, ec_id):
329
    """Reserve a MAC for an instance.
330

331
    This only checks instances managed by this cluster, it does not
332
    check for potential collisions elsewhere.
333

334
    """
335
    all_macs = self._AllMACs()
336
    if mac in all_macs:
337
      raise errors.ReservationError("mac already in use")
338
    else:
339
      self._temporary_macs.Reserve(ec_id, mac)
340

    
341
  def _UnlockedCommitTemporaryIps(self, ec_id):
342
    """Commit all reserved IP address to their respective pools
343

344
    """
345
    for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
346
      self._UnlockedCommitIp(action, net_uuid, address)
347

    
348
  def _UnlockedCommitIp(self, action, net_uuid, address):
349
    """Commit a reserved IP address to an IP pool.
350

351
    The IP address is taken from the network's IP pool and marked as reserved.
352

353
    """
354
    nobj = self._UnlockedGetNetwork(net_uuid)
355
    pool = network.AddressPool(nobj)
356
    if action == constants.RESERVE_ACTION:
357
      pool.Reserve(address)
358
    elif action == constants.RELEASE_ACTION:
359
      pool.Release(address)
360

    
361
  def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
362
    """Give a specific IP address back to an IP pool.
363

364
    The IP address is returned to the IP pool designated by pool_id and marked
365
    as reserved.
366

367
    """
368
    self._temporary_ips.Reserve(ec_id,
369
                                (constants.RELEASE_ACTION, address, net_uuid))
370

    
371
  @locking.ssynchronized(_config_lock, shared=1)
372
  def ReleaseIp(self, net_uuid, address, ec_id):
373
    """Give a specified IP address back to an IP pool.
374

375
    This is just a wrapper around _UnlockedReleaseIp.
376

377
    """
378
    if net_uuid:
379
      self._UnlockedReleaseIp(net_uuid, address, ec_id)
380

    
381
  @locking.ssynchronized(_config_lock, shared=1)
382
  def GenerateIp(self, net_uuid, ec_id):
383
    """Find a free IPv4 address for an instance.
384

385
    """
386
    nobj = self._UnlockedGetNetwork(net_uuid)
387
    pool = network.AddressPool(nobj)
388

    
389
    def gen_one():
390
      try:
391
        ip = pool.GenerateFree()
392
      except errors.AddressPoolError:
393
        raise errors.ReservationError("Cannot generate IP. Network is full")
394
      return (constants.RESERVE_ACTION, ip, net_uuid)
395

    
396
    _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
397
    return address
398

    
399
  def _UnlockedReserveIp(self, net_uuid, address, ec_id, check=True):
400
    """Reserve a given IPv4 address for use by an instance.
401

402
    """
403
    nobj = self._UnlockedGetNetwork(net_uuid)
404
    pool = network.AddressPool(nobj)
405
    try:
406
      isreserved = pool.IsReserved(address)
407
      isextreserved = pool.IsReserved(address, external=True)
408
    except errors.AddressPoolError:
409
      raise errors.ReservationError("IP address not in network")
410
    if isreserved:
411
      raise errors.ReservationError("IP address already in use")
412
    if check and isextreserved:
413
      raise errors.ReservationError("IP is externally reserved")
414

    
415
    return self._temporary_ips.Reserve(ec_id,
416
                                       (constants.RESERVE_ACTION,
417
                                        address, net_uuid))
418

    
419
  @locking.ssynchronized(_config_lock, shared=1)
420
  def ReserveIp(self, net_uuid, address, ec_id, check=True):
421
    """Reserve a given IPv4 address for use by an instance.
422

423
    """
424
    if net_uuid:
425
      return self._UnlockedReserveIp(net_uuid, address, ec_id, check)
426

    
427
  @locking.ssynchronized(_config_lock, shared=1)
428
  def ReserveLV(self, lv_name, ec_id):
429
    """Reserve an VG/LV pair for an instance.
430

431
    @type lv_name: string
432
    @param lv_name: the logical volume name to reserve
433

434
    """
435
    all_lvs = self._AllLVs()
436
    if lv_name in all_lvs:
437
      raise errors.ReservationError("LV already in use")
438
    else:
439
      self._temporary_lvs.Reserve(ec_id, lv_name)
440

    
441
  @locking.ssynchronized(_config_lock, shared=1)
442
  def GenerateDRBDSecret(self, ec_id):
443
    """Generate a DRBD secret.
444

445
    This checks the current disks for duplicates.
446

447
    """
448
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
449
                                            utils.GenerateSecret,
450
                                            ec_id)
451

    
452
  def _AllLVs(self):
453
    """Compute the list of all LVs.
454

455
    """
456
    lvnames = set()
457
    for instance in self._config_data.instances.values():
458
      node_data = instance.MapLVsByNode()
459
      for lv_list in node_data.values():
460
        lvnames.update(lv_list)
461
    return lvnames
462

    
463
  def _AllDisks(self):
464
    """Compute the list of all Disks (recursively, including children).
465

466
    """
467
    def DiskAndAllChildren(disk):
468
      """Returns a list containing the given disk and all of his children.
469

470
      """
471
      disks = [disk]
472
      if disk.children:
473
        for child_disk in disk.children:
474
          disks.extend(DiskAndAllChildren(child_disk))
475
      return disks
476

    
477
    disks = []
478
    for instance in self._config_data.instances.values():
479
      for disk in instance.disks:
480
        disks.extend(DiskAndAllChildren(disk))
481
    return disks
482

    
483
  def _AllNICs(self):
484
    """Compute the list of all NICs.
485

486
    """
487
    nics = []
488
    for instance in self._config_data.instances.values():
489
      nics.extend(instance.nics)
490
    return nics
491

    
492
  def _AllIDs(self, include_temporary):
493
    """Compute the list of all UUIDs and names we have.
494

495
    @type include_temporary: boolean
496
    @param include_temporary: whether to include the _temporary_ids set
497
    @rtype: set
498
    @return: a set of IDs
499

500
    """
501
    existing = set()
502
    if include_temporary:
503
      existing.update(self._temporary_ids.GetReserved())
504
    existing.update(self._AllLVs())
505
    existing.update(self._config_data.instances.keys())
506
    existing.update(self._config_data.nodes.keys())
507
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
508
    return existing
509

    
510
  def _GenerateUniqueID(self, ec_id):
511
    """Generate an unique UUID.
512

513
    This checks the current node, instances and disk names for
514
    duplicates.
515

516
    @rtype: string
517
    @return: the unique id
518

519
    """
520
    existing = self._AllIDs(include_temporary=False)
521
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
522

    
523
  @locking.ssynchronized(_config_lock, shared=1)
524
  def GenerateUniqueID(self, ec_id):
525
    """Generate an unique ID.
526

527
    This is just a wrapper over the unlocked version.
528

529
    @type ec_id: string
530
    @param ec_id: unique id for the job to reserve the id to
531

532
    """
533
    return self._GenerateUniqueID(ec_id)
534

    
535
  def _AllMACs(self):
536
    """Return all MACs present in the config.
537

538
    @rtype: list
539
    @return: the list of all MACs
540

541
    """
542
    result = []
543
    for instance in self._config_data.instances.values():
544
      for nic in instance.nics:
545
        result.append(nic.mac)
546

    
547
    return result
548

    
549
  def _AllDRBDSecrets(self):
550
    """Return all DRBD secrets present in the config.
551

552
    @rtype: list
553
    @return: the list of all DRBD secrets
554

555
    """
556
    def helper(disk, result):
557
      """Recursively gather secrets from this disk."""
558
      if disk.dev_type == constants.DT_DRBD8:
559
        result.append(disk.logical_id[5])
560
      if disk.children:
561
        for child in disk.children:
562
          helper(child, result)
563

    
564
    result = []
565
    for instance in self._config_data.instances.values():
566
      for disk in instance.disks:
567
        helper(disk, result)
568

    
569
    return result
570

    
571
  def _CheckDiskIDs(self, disk, l_ids):
572
    """Compute duplicate disk IDs
573

574
    @type disk: L{objects.Disk}
575
    @param disk: the disk at which to start searching
576
    @type l_ids: list
577
    @param l_ids: list of current logical ids
578
    @rtype: list
579
    @return: a list of error messages
580

581
    """
582
    result = []
583
    if disk.logical_id is not None:
584
      if disk.logical_id in l_ids:
585
        result.append("duplicate logical id %s" % str(disk.logical_id))
586
      else:
587
        l_ids.append(disk.logical_id)
588

    
589
    if disk.children:
590
      for child in disk.children:
591
        result.extend(self._CheckDiskIDs(child, l_ids))
592
    return result
593

    
594
  def _UnlockedVerifyConfig(self):
595
    """Verify function.
596

597
    @rtype: list
598
    @return: a list of error messages; a non-empty list signifies
599
        configuration errors
600

601
    """
602
    # pylint: disable=R0914
603
    result = []
604
    seen_macs = []
605
    ports = {}
606
    data = self._config_data
607
    cluster = data.cluster
608
    seen_lids = []
609

    
610
    # global cluster checks
611
    if not cluster.enabled_hypervisors:
612
      result.append("enabled hypervisors list doesn't have any entries")
613
    invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
614
    if invalid_hvs:
615
      result.append("enabled hypervisors contains invalid entries: %s" %
616
                    utils.CommaJoin(invalid_hvs))
617
    missing_hvp = (set(cluster.enabled_hypervisors) -
618
                   set(cluster.hvparams.keys()))
619
    if missing_hvp:
620
      result.append("hypervisor parameters missing for the enabled"
621
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
622

    
623
    if not cluster.enabled_disk_templates:
624
      result.append("enabled disk templates list doesn't have any entries")
625
    invalid_disk_templates = set(cluster.enabled_disk_templates) \
626
                               - constants.DISK_TEMPLATES
627
    if invalid_disk_templates:
628
      result.append("enabled disk templates list contains invalid entries:"
629
                    " %s" % utils.CommaJoin(invalid_disk_templates))
630

    
631
    if cluster.master_node not in data.nodes:
632
      result.append("cluster has invalid primary node '%s'" %
633
                    cluster.master_node)
634

    
635
    def _helper(owner, attr, value, template):
636
      try:
637
        utils.ForceDictType(value, template)
638
      except errors.GenericError, err:
639
        result.append("%s has invalid %s: %s" % (owner, attr, err))
640

    
641
    def _helper_nic(owner, params):
642
      try:
643
        objects.NIC.CheckParameterSyntax(params)
644
      except errors.ConfigurationError, err:
645
        result.append("%s has invalid nicparams: %s" % (owner, err))
646

    
647
    def _helper_ipolicy(owner, ipolicy, iscluster):
648
      try:
649
        objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
650
      except errors.ConfigurationError, err:
651
        result.append("%s has invalid instance policy: %s" % (owner, err))
652
      for key, value in ipolicy.items():
653
        if key == constants.ISPECS_MINMAX:
654
          for k in range(len(value)):
655
            _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
656
        elif key == constants.ISPECS_STD:
657
          _helper(owner, "ipolicy/" + key, value,
658
                  constants.ISPECS_PARAMETER_TYPES)
659
        else:
660
          # FIXME: assuming list type
661
          if key in constants.IPOLICY_PARAMETERS:
662
            exp_type = float
663
            # if the value is int, it can be converted into float
664
            convertible_types = [int]
665
          else:
666
            exp_type = list
667
            convertible_types = []
668
          # Try to convert from allowed types, if necessary.
669
          if any(isinstance(value, ct) for ct in convertible_types):
670
            try:
671
              value = exp_type(value)
672
              ipolicy[key] = value
673
            except ValueError:
674
              pass
675
          if not isinstance(value, exp_type):
676
            result.append("%s has invalid instance policy: for %s,"
677
                          " expecting %s, got %s" %
678
                          (owner, key, exp_type.__name__, type(value)))
679

    
680
    def _helper_ispecs(owner, parentkey, params):
681
      for (key, value) in params.items():
682
        fullkey = "/".join([parentkey, key])
683
        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
684

    
685
    # check cluster parameters
686
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
687
            constants.BES_PARAMETER_TYPES)
688
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
689
            constants.NICS_PARAMETER_TYPES)
690
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
691
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
692
            constants.NDS_PARAMETER_TYPES)
693
    _helper_ipolicy("cluster", cluster.ipolicy, True)
694

    
695
    for disk_template in cluster.diskparams:
696
      if disk_template not in constants.DTS_HAVE_ACCESS:
697
        continue
698

    
699
      access = cluster.diskparams[disk_template].get(constants.LDP_ACCESS,
700
                                                     constants.DISK_KERNELSPACE)
701
      if access not in constants.DISK_VALID_ACCESS_MODES:
702
        result.append(
703
          "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
704
            disk_template, constants.LDP_ACCESS, access,
705
            utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
706
          )
707
        )
708

    
709
    # per-instance checks
710
    for instance_uuid in data.instances:
711
      instance = data.instances[instance_uuid]
712
      if instance.uuid != instance_uuid:
713
        result.append("instance '%s' is indexed by wrong UUID '%s'" %
714
                      (instance.name, instance_uuid))
715
      if instance.primary_node not in data.nodes:
716
        result.append("instance '%s' has invalid primary node '%s'" %
717
                      (instance.name, instance.primary_node))
718
      for snode in instance.secondary_nodes:
719
        if snode not in data.nodes:
720
          result.append("instance '%s' has invalid secondary node '%s'" %
721
                        (instance.name, snode))
722
      for idx, nic in enumerate(instance.nics):
723
        if nic.mac in seen_macs:
724
          result.append("instance '%s' has NIC %d mac %s duplicate" %
725
                        (instance.name, idx, nic.mac))
726
        else:
727
          seen_macs.append(nic.mac)
728
        if nic.nicparams:
729
          filled = cluster.SimpleFillNIC(nic.nicparams)
730
          owner = "instance %s nic %d" % (instance.name, idx)
731
          _helper(owner, "nicparams",
732
                  filled, constants.NICS_PARAMETER_TYPES)
733
          _helper_nic(owner, filled)
734

    
735
      # disk template checks
736
      if not instance.disk_template in data.cluster.enabled_disk_templates:
737
        result.append("instance '%s' uses the disabled disk template '%s'." %
738
                      (instance.name, instance.disk_template))
739

    
740
      # parameter checks
741
      if instance.beparams:
742
        _helper("instance %s" % instance.name, "beparams",
743
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
744

    
745
      # gather the drbd ports for duplicate checks
746
      for (idx, dsk) in enumerate(instance.disks):
747
        if dsk.dev_type in constants.DTS_DRBD:
748
          tcp_port = dsk.logical_id[2]
749
          if tcp_port not in ports:
750
            ports[tcp_port] = []
751
          ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
752
      # gather network port reservation
753
      net_port = getattr(instance, "network_port", None)
754
      if net_port is not None:
755
        if net_port not in ports:
756
          ports[net_port] = []
757
        ports[net_port].append((instance.name, "network port"))
758

    
759
      # instance disk verify
760
      for idx, disk in enumerate(instance.disks):
761
        result.extend(["instance '%s' disk %d error: %s" %
762
                       (instance.name, idx, msg) for msg in disk.Verify()])
763
        result.extend(self._CheckDiskIDs(disk, seen_lids))
764

    
765
      wrong_names = _CheckInstanceDiskIvNames(instance.disks)
766
      if wrong_names:
767
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
768
                         (idx, exp_name, actual_name))
769
                        for (idx, exp_name, actual_name) in wrong_names)
770

    
771
        result.append("Instance '%s' has wrongly named disks: %s" %
772
                      (instance.name, tmp))
773

    
774
    # cluster-wide pool of free ports
775
    for free_port in cluster.tcpudp_port_pool:
776
      if free_port not in ports:
777
        ports[free_port] = []
778
      ports[free_port].append(("cluster", "port marked as free"))
779

    
780
    # compute tcp/udp duplicate ports
781
    keys = ports.keys()
782
    keys.sort()
783
    for pnum in keys:
784
      pdata = ports[pnum]
785
      if len(pdata) > 1:
786
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
787
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
788

    
789
    # highest used tcp port check
790
    if keys:
791
      if keys[-1] > cluster.highest_used_port:
792
        result.append("Highest used port mismatch, saved %s, computed %s" %
793
                      (cluster.highest_used_port, keys[-1]))
794

    
795
    if not data.nodes[cluster.master_node].master_candidate:
796
      result.append("Master node is not a master candidate")
797

    
798
    # master candidate checks
799
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
800
    if mc_now < mc_max:
801
      result.append("Not enough master candidates: actual %d, target %d" %
802
                    (mc_now, mc_max))
803

    
804
    # node checks
805
    for node_uuid, node in data.nodes.items():
806
      if node.uuid != node_uuid:
807
        result.append("Node '%s' is indexed by wrong UUID '%s'" %
808
                      (node.name, node_uuid))
809
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
810
        result.append("Node %s state is invalid: master_candidate=%s,"
811
                      " drain=%s, offline=%s" %
812
                      (node.name, node.master_candidate, node.drained,
813
                       node.offline))
814
      if node.group not in data.nodegroups:
815
        result.append("Node '%s' has invalid group '%s'" %
816
                      (node.name, node.group))
817
      else:
818
        _helper("node %s" % node.name, "ndparams",
819
                cluster.FillND(node, data.nodegroups[node.group]),
820
                constants.NDS_PARAMETER_TYPES)
821
      used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
822
      if used_globals:
823
        result.append("Node '%s' has some global parameters set: %s" %
824
                      (node.name, utils.CommaJoin(used_globals)))
825

    
826
    # nodegroups checks
827
    nodegroups_names = set()
828
    for nodegroup_uuid in data.nodegroups:
829
      nodegroup = data.nodegroups[nodegroup_uuid]
830
      if nodegroup.uuid != nodegroup_uuid:
831
        result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
832
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
833
      if utils.UUID_RE.match(nodegroup.name.lower()):
834
        result.append("node group '%s' (uuid: '%s') has uuid-like name" %
835
                      (nodegroup.name, nodegroup.uuid))
836
      if nodegroup.name in nodegroups_names:
837
        result.append("duplicate node group name '%s'" % nodegroup.name)
838
      else:
839
        nodegroups_names.add(nodegroup.name)
840
      group_name = "group %s" % nodegroup.name
841
      _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
842
                      False)
843
      if nodegroup.ndparams:
844
        _helper(group_name, "ndparams",
845
                cluster.SimpleFillND(nodegroup.ndparams),
846
                constants.NDS_PARAMETER_TYPES)
847

    
848
    # drbd minors check
849
    _, duplicates = self._UnlockedComputeDRBDMap()
850
    for node, minor, instance_a, instance_b in duplicates:
851
      result.append("DRBD minor %d on node %s is assigned twice to instances"
852
                    " %s and %s" % (minor, node, instance_a, instance_b))
853

    
854
    # IP checks
855
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
856
    ips = {}
857

    
858
    def _AddIpAddress(ip, name):
859
      ips.setdefault(ip, []).append(name)
860

    
861
    _AddIpAddress(cluster.master_ip, "cluster_ip")
862

    
863
    for node in data.nodes.values():
864
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
865
      if node.secondary_ip != node.primary_ip:
866
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
867

    
868
    for instance in data.instances.values():
869
      for idx, nic in enumerate(instance.nics):
870
        if nic.ip is None:
871
          continue
872

    
873
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
874
        nic_mode = nicparams[constants.NIC_MODE]
875
        nic_link = nicparams[constants.NIC_LINK]
876

    
877
        if nic_mode == constants.NIC_MODE_BRIDGED:
878
          link = "bridge:%s" % nic_link
879
        elif nic_mode == constants.NIC_MODE_ROUTED:
880
          link = "route:%s" % nic_link
881
        else:
882
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
883

    
884
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
885
                      "instance:%s/nic:%d" % (instance.name, idx))
886

    
887
    for ip, owners in ips.items():
888
      if len(owners) > 1:
889
        result.append("IP address %s is used by multiple owners: %s" %
890
                      (ip, utils.CommaJoin(owners)))
891

    
892
    return result
893

    
894
  @locking.ssynchronized(_config_lock, shared=1)
895
  def VerifyConfig(self):
896
    """Verify function.
897

898
    This is just a wrapper over L{_UnlockedVerifyConfig}.
899

900
    @rtype: list
901
    @return: a list of error messages; a non-empty list signifies
902
        configuration errors
903

904
    """
905
    return self._UnlockedVerifyConfig()
906

    
907
  @locking.ssynchronized(_config_lock)
908
  def AddTcpUdpPort(self, port):
909
    """Adds a new port to the available port pool.
910

911
    @warning: this method does not "flush" the configuration (via
912
        L{_WriteConfig}); callers should do that themselves once the
913
        configuration is stable
914

915
    """
916
    if not isinstance(port, int):
917
      raise errors.ProgrammerError("Invalid type passed for port")
918

    
919
    self._config_data.cluster.tcpudp_port_pool.add(port)
920

    
921
  @locking.ssynchronized(_config_lock, shared=1)
922
  def GetPortList(self):
923
    """Returns a copy of the current port list.
924

925
    """
926
    return self._config_data.cluster.tcpudp_port_pool.copy()
927

    
928
  @locking.ssynchronized(_config_lock)
929
  def AllocatePort(self):
930
    """Allocate a port.
931

932
    The port will be taken from the available port pool or from the
933
    default port range (and in this case we increase
934
    highest_used_port).
935

936
    """
937
    # If there are TCP/IP ports configured, we use them first.
938
    if self._config_data.cluster.tcpudp_port_pool:
939
      port = self._config_data.cluster.tcpudp_port_pool.pop()
940
    else:
941
      port = self._config_data.cluster.highest_used_port + 1
942
      if port >= constants.LAST_DRBD_PORT:
943
        raise errors.ConfigurationError("The highest used port is greater"
944
                                        " than %s. Aborting." %
945
                                        constants.LAST_DRBD_PORT)
946
      self._config_data.cluster.highest_used_port = port
947

    
948
    self._WriteConfig()
949
    return port
950

    
951
  def _UnlockedComputeDRBDMap(self):
952
    """Compute the used DRBD minor/nodes.
953

954
    @rtype: (dict, list)
955
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
956
        the returned dict will have all the nodes in it (even if with
957
        an empty list), and a list of duplicates; if the duplicates
958
        list is not empty, the configuration is corrupted and its caller
959
        should raise an exception
960

961
    """
962
    def _AppendUsedMinors(get_node_name_fn, instance, disk, used):
963
      duplicates = []
964
      if disk.dev_type == constants.DT_DRBD8 and len(disk.logical_id) >= 5:
965
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
966
        for node_uuid, minor in ((node_a, minor_a), (node_b, minor_b)):
967
          assert node_uuid in used, \
968
            ("Node '%s' of instance '%s' not found in node list" %
969
             (get_node_name_fn(node_uuid), instance.name))
970
          if minor in used[node_uuid]:
971
            duplicates.append((node_uuid, minor, instance.uuid,
972
                               used[node_uuid][minor]))
973
          else:
974
            used[node_uuid][minor] = instance.uuid
975
      if disk.children:
976
        for child in disk.children:
977
          duplicates.extend(_AppendUsedMinors(get_node_name_fn, instance, child,
978
                                              used))
979
      return duplicates
980

    
981
    duplicates = []
982
    my_dict = dict((node_uuid, {}) for node_uuid in self._config_data.nodes)
983
    for instance in self._config_data.instances.itervalues():
984
      for disk in instance.disks:
985
        duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
986
                                            instance, disk, my_dict))
987
    for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
988
      if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
989
        duplicates.append((node_uuid, minor, inst_uuid,
990
                           my_dict[node_uuid][minor]))
991
      else:
992
        my_dict[node_uuid][minor] = inst_uuid
993
    return my_dict, duplicates
994

    
995
  @locking.ssynchronized(_config_lock)
996
  def ComputeDRBDMap(self):
997
    """Compute the used DRBD minor/nodes.
998

999
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
1000

1001
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
1002
        the returned dict will have all the nodes in it (even if with
1003
        an empty list).
1004

1005
    """
1006
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1007
    if duplicates:
1008
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1009
                                      str(duplicates))
1010
    return d_map
1011

    
1012
  @locking.ssynchronized(_config_lock)
1013
  def AllocateDRBDMinor(self, node_uuids, inst_uuid):
1014
    """Allocate a drbd minor.
1015

1016
    The free minor will be automatically computed from the existing
1017
    devices. A node can be given multiple times in order to allocate
1018
    multiple minors. The result is the list of minors, in the same
1019
    order as the passed nodes.
1020

1021
    @type inst_uuid: string
1022
    @param inst_uuid: the instance for which we allocate minors
1023

1024
    """
1025
    assert isinstance(inst_uuid, basestring), \
1026
           "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
1027

    
1028
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1029
    if duplicates:
1030
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1031
                                      str(duplicates))
1032
    result = []
1033
    for nuuid in node_uuids:
1034
      ndata = d_map[nuuid]
1035
      if not ndata:
1036
        # no minors used, we can start at 0
1037
        result.append(0)
1038
        ndata[0] = inst_uuid
1039
        self._temporary_drbds[(nuuid, 0)] = inst_uuid
1040
        continue
1041
      keys = ndata.keys()
1042
      keys.sort()
1043
      ffree = utils.FirstFree(keys)
1044
      if ffree is None:
1045
        # return the next minor
1046
        # TODO: implement high-limit check
1047
        minor = keys[-1] + 1
1048
      else:
1049
        minor = ffree
1050
      # double-check minor against current instances
1051
      assert minor not in d_map[nuuid], \
1052
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
1053
              " already allocated to instance %s" %
1054
              (minor, nuuid, d_map[nuuid][minor]))
1055
      ndata[minor] = inst_uuid
1056
      # double-check minor against reservation
1057
      r_key = (nuuid, minor)
1058
      assert r_key not in self._temporary_drbds, \
1059
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
1060
              " reserved for instance %s" %
1061
              (minor, nuuid, self._temporary_drbds[r_key]))
1062
      self._temporary_drbds[r_key] = inst_uuid
1063
      result.append(minor)
1064
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1065
                  node_uuids, result)
1066
    return result
1067

    
1068
  def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1069
    """Release temporary drbd minors allocated for a given instance.
1070

1071
    @type inst_uuid: string
1072
    @param inst_uuid: the instance for which temporary minors should be
1073
                      released
1074

1075
    """
1076
    assert isinstance(inst_uuid, basestring), \
1077
           "Invalid argument passed to ReleaseDRBDMinors"
1078
    for key, uuid in self._temporary_drbds.items():
1079
      if uuid == inst_uuid:
1080
        del self._temporary_drbds[key]
1081

    
1082
  @locking.ssynchronized(_config_lock)
1083
  def ReleaseDRBDMinors(self, inst_uuid):
1084
    """Release temporary drbd minors allocated for a given instance.
1085

1086
    This should be called on the error paths, on the success paths
1087
    it's automatically called by the ConfigWriter add and update
1088
    functions.
1089

1090
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1091

1092
    @type inst_uuid: string
1093
    @param inst_uuid: the instance for which temporary minors should be
1094
                      released
1095

1096
    """
1097
    self._UnlockedReleaseDRBDMinors(inst_uuid)
1098

    
1099
  @locking.ssynchronized(_config_lock, shared=1)
1100
  def GetConfigVersion(self):
1101
    """Get the configuration version.
1102

1103
    @return: Config version
1104

1105
    """
1106
    return self._config_data.version
1107

    
1108
  @locking.ssynchronized(_config_lock, shared=1)
1109
  def GetClusterName(self):
1110
    """Get cluster name.
1111

1112
    @return: Cluster name
1113

1114
    """
1115
    return self._config_data.cluster.cluster_name
1116

    
1117
  @locking.ssynchronized(_config_lock, shared=1)
1118
  def GetMasterNode(self):
1119
    """Get the UUID of the master node for this cluster.
1120

1121
    @return: Master node UUID
1122

1123
    """
1124
    return self._config_data.cluster.master_node
1125

    
1126
  @locking.ssynchronized(_config_lock, shared=1)
1127
  def GetMasterNodeName(self):
1128
    """Get the hostname of the master node for this cluster.
1129

1130
    @return: Master node hostname
1131

1132
    """
1133
    return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
1134

    
1135
  @locking.ssynchronized(_config_lock, shared=1)
1136
  def GetMasterNodeInfo(self):
1137
    """Get the master node information for this cluster.
1138

1139
    @rtype: objects.Node
1140
    @return: Master node L{objects.Node} object
1141

1142
    """
1143
    return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
1144

    
1145
  @locking.ssynchronized(_config_lock, shared=1)
1146
  def GetMasterIP(self):
1147
    """Get the IP of the master node for this cluster.
1148

1149
    @return: Master IP
1150

1151
    """
1152
    return self._config_data.cluster.master_ip
1153

    
1154
  @locking.ssynchronized(_config_lock, shared=1)
1155
  def GetMasterNetdev(self):
1156
    """Get the master network device for this cluster.
1157

1158
    """
1159
    return self._config_data.cluster.master_netdev
1160

    
1161
  @locking.ssynchronized(_config_lock, shared=1)
1162
  def GetMasterNetmask(self):
1163
    """Get the netmask of the master node for this cluster.
1164

1165
    """
1166
    return self._config_data.cluster.master_netmask
1167

    
1168
  @locking.ssynchronized(_config_lock, shared=1)
1169
  def GetUseExternalMipScript(self):
1170
    """Get flag representing whether to use the external master IP setup script.
1171

1172
    """
1173
    return self._config_data.cluster.use_external_mip_script
1174

    
1175
  @locking.ssynchronized(_config_lock, shared=1)
1176
  def GetFileStorageDir(self):
1177
    """Get the file storage dir for this cluster.
1178

1179
    """
1180
    return self._config_data.cluster.file_storage_dir
1181

    
1182
  @locking.ssynchronized(_config_lock, shared=1)
1183
  def GetSharedFileStorageDir(self):
1184
    """Get the shared file storage dir for this cluster.
1185

1186
    """
1187
    return self._config_data.cluster.shared_file_storage_dir
1188

    
1189
  @locking.ssynchronized(_config_lock, shared=1)
1190
  def GetGlusterStorageDir(self):
1191
    """Get the Gluster storage dir for this cluster.
1192

1193
    """
1194
    return self._config_data.cluster.gluster_storage_dir
1195

    
1196
  @locking.ssynchronized(_config_lock, shared=1)
1197
  def GetHypervisorType(self):
1198
    """Get the hypervisor type for this cluster.
1199

1200
    """
1201
    return self._config_data.cluster.enabled_hypervisors[0]
1202

    
1203
  @locking.ssynchronized(_config_lock, shared=1)
1204
  def GetRsaHostKey(self):
1205
    """Return the rsa hostkey from the config.
1206

1207
    @rtype: string
1208
    @return: the rsa hostkey
1209

1210
    """
1211
    return self._config_data.cluster.rsahostkeypub
1212

    
1213
  @locking.ssynchronized(_config_lock, shared=1)
1214
  def GetDsaHostKey(self):
1215
    """Return the dsa hostkey from the config.
1216

1217
    @rtype: string
1218
    @return: the dsa hostkey
1219

1220
    """
1221
    return self._config_data.cluster.dsahostkeypub
1222

    
1223
  @locking.ssynchronized(_config_lock, shared=1)
1224
  def GetDefaultIAllocator(self):
1225
    """Get the default instance allocator for this cluster.
1226

1227
    """
1228
    return self._config_data.cluster.default_iallocator
1229

    
1230
  @locking.ssynchronized(_config_lock, shared=1)
1231
  def GetDefaultIAllocatorParameters(self):
1232
    """Get the default instance allocator parameters for this cluster.
1233

1234
    @rtype: dict
1235
    @return: dict of iallocator parameters
1236

1237
    """
1238
    return self._config_data.cluster.default_iallocator_params
1239

    
1240
  @locking.ssynchronized(_config_lock, shared=1)
1241
  def GetPrimaryIPFamily(self):
1242
    """Get cluster primary ip family.
1243

1244
    @return: primary ip family
1245

1246
    """
1247
    return self._config_data.cluster.primary_ip_family
1248

    
1249
  @locking.ssynchronized(_config_lock, shared=1)
1250
  def GetMasterNetworkParameters(self):
1251
    """Get network parameters of the master node.
1252

1253
    @rtype: L{object.MasterNetworkParameters}
1254
    @return: network parameters of the master node
1255

1256
    """
1257
    cluster = self._config_data.cluster
1258
    result = objects.MasterNetworkParameters(
1259
      uuid=cluster.master_node, ip=cluster.master_ip,
1260
      netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1261
      ip_family=cluster.primary_ip_family)
1262

    
1263
    return result
1264

    
1265
  @locking.ssynchronized(_config_lock, shared=1)
1266
  def GetInstanceCommunicationNetwork(self):
1267
    """Get cluster instance communication network
1268

1269
    @rtype: string
1270
    @return: instance communication network, which is the name of the
1271
             network used for instance communication
1272

1273
    """
1274
    return self._config_data.cluster.instance_communication_network
1275

    
1276
  @locking.ssynchronized(_config_lock)
1277
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1278
    """Add a node group to the configuration.
1279

1280
    This method calls group.UpgradeConfig() to fill any missing attributes
1281
    according to their default values.
1282

1283
    @type group: L{objects.NodeGroup}
1284
    @param group: the NodeGroup object to add
1285
    @type ec_id: string
1286
    @param ec_id: unique id for the job to use when creating a missing UUID
1287
    @type check_uuid: bool
1288
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1289
                       it does, ensure that it does not exist in the
1290
                       configuration already
1291

1292
    """
1293
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1294
    self._WriteConfig()
1295

    
1296
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1297
    """Add a node group to the configuration.
1298

1299
    """
1300
    logging.info("Adding node group %s to configuration", group.name)
1301

    
1302
    # Some code might need to add a node group with a pre-populated UUID
1303
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1304
    # the "does this UUID" exist already check.
1305
    if check_uuid:
1306
      self._EnsureUUID(group, ec_id)
1307

    
1308
    try:
1309
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1310
    except errors.OpPrereqError:
1311
      pass
1312
    else:
1313
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1314
                                 " node group (UUID: %s)" %
1315
                                 (group.name, existing_uuid),
1316
                                 errors.ECODE_EXISTS)
1317

    
1318
    group.serial_no = 1
1319
    group.ctime = group.mtime = time.time()
1320
    group.UpgradeConfig()
1321

    
1322
    self._config_data.nodegroups[group.uuid] = group
1323
    self._config_data.cluster.serial_no += 1
1324

    
1325
  @locking.ssynchronized(_config_lock)
1326
  def RemoveNodeGroup(self, group_uuid):
1327
    """Remove a node group from the configuration.
1328

1329
    @type group_uuid: string
1330
    @param group_uuid: the UUID of the node group to remove
1331

1332
    """
1333
    logging.info("Removing node group %s from configuration", group_uuid)
1334

    
1335
    if group_uuid not in self._config_data.nodegroups:
1336
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1337

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

    
1341
    del self._config_data.nodegroups[group_uuid]
1342
    self._config_data.cluster.serial_no += 1
1343
    self._WriteConfig()
1344

    
1345
  def _UnlockedLookupNodeGroup(self, target):
1346
    """Lookup a node group's UUID.
1347

1348
    @type target: string or None
1349
    @param target: group name or UUID or None to look for the default
1350
    @rtype: string
1351
    @return: nodegroup UUID
1352
    @raises errors.OpPrereqError: when the target group cannot be found
1353

1354
    """
1355
    if target is None:
1356
      if len(self._config_data.nodegroups) != 1:
1357
        raise errors.OpPrereqError("More than one node group exists. Target"
1358
                                   " group must be specified explicitly.")
1359
      else:
1360
        return self._config_data.nodegroups.keys()[0]
1361
    if target in self._config_data.nodegroups:
1362
      return target
1363
    for nodegroup in self._config_data.nodegroups.values():
1364
      if nodegroup.name == target:
1365
        return nodegroup.uuid
1366
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1367
                               errors.ECODE_NOENT)
1368

    
1369
  @locking.ssynchronized(_config_lock, shared=1)
1370
  def LookupNodeGroup(self, target):
1371
    """Lookup a node group's UUID.
1372

1373
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1374

1375
    @type target: string or None
1376
    @param target: group name or UUID or None to look for the default
1377
    @rtype: string
1378
    @return: nodegroup UUID
1379

1380
    """
1381
    return self._UnlockedLookupNodeGroup(target)
1382

    
1383
  def _UnlockedGetNodeGroup(self, uuid):
1384
    """Lookup a node group.
1385

1386
    @type uuid: string
1387
    @param uuid: group UUID
1388
    @rtype: L{objects.NodeGroup} or None
1389
    @return: nodegroup object, or None if not found
1390

1391
    """
1392
    if uuid not in self._config_data.nodegroups:
1393
      return None
1394

    
1395
    return self._config_data.nodegroups[uuid]
1396

    
1397
  @locking.ssynchronized(_config_lock, shared=1)
1398
  def GetNodeGroup(self, uuid):
1399
    """Lookup a node group.
1400

1401
    @type uuid: string
1402
    @param uuid: group UUID
1403
    @rtype: L{objects.NodeGroup} or None
1404
    @return: nodegroup object, or None if not found
1405

1406
    """
1407
    return self._UnlockedGetNodeGroup(uuid)
1408

    
1409
  def _UnlockedGetAllNodeGroupsInfo(self):
1410
    """Get the configuration of all node groups.
1411

1412
    """
1413
    return dict(self._config_data.nodegroups)
1414

    
1415
  @locking.ssynchronized(_config_lock, shared=1)
1416
  def GetAllNodeGroupsInfo(self):
1417
    """Get the configuration of all node groups.
1418

1419
    """
1420
    return self._UnlockedGetAllNodeGroupsInfo()
1421

    
1422
  @locking.ssynchronized(_config_lock, shared=1)
1423
  def GetAllNodeGroupsInfoDict(self):
1424
    """Get the configuration of all node groups expressed as a dictionary of
1425
    dictionaries.
1426

1427
    """
1428
    return dict(map(lambda (uuid, ng): (uuid, ng.ToDict()),
1429
                    self._UnlockedGetAllNodeGroupsInfo().items()))
1430

    
1431
  @locking.ssynchronized(_config_lock, shared=1)
1432
  def GetNodeGroupList(self):
1433
    """Get a list of node groups.
1434

1435
    """
1436
    return self._config_data.nodegroups.keys()
1437

    
1438
  @locking.ssynchronized(_config_lock, shared=1)
1439
  def GetNodeGroupMembersByNodes(self, nodes):
1440
    """Get nodes which are member in the same nodegroups as the given nodes.
1441

1442
    """
1443
    ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1444
    return frozenset(member_uuid
1445
                     for node_uuid in nodes
1446
                     for member_uuid in
1447
                       self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1448

    
1449
  @locking.ssynchronized(_config_lock, shared=1)
1450
  def GetMultiNodeGroupInfo(self, group_uuids):
1451
    """Get the configuration of multiple node groups.
1452

1453
    @param group_uuids: List of node group UUIDs
1454
    @rtype: list
1455
    @return: List of tuples of (group_uuid, group_info)
1456

1457
    """
1458
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1459

    
1460
  @locking.ssynchronized(_config_lock)
1461
  def AddInstance(self, instance, ec_id):
1462
    """Add an instance to the config.
1463

1464
    This should be used after creating a new instance.
1465

1466
    @type instance: L{objects.Instance}
1467
    @param instance: the instance object
1468

1469
    """
1470
    if not isinstance(instance, objects.Instance):
1471
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1472

    
1473
    if instance.disk_template != constants.DT_DISKLESS:
1474
      all_lvs = instance.MapLVsByNode()
1475
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1476

    
1477
    all_macs = self._AllMACs()
1478
    for nic in instance.nics:
1479
      if nic.mac in all_macs:
1480
        raise errors.ConfigurationError("Cannot add instance %s:"
1481
                                        " MAC address '%s' already in use." %
1482
                                        (instance.name, nic.mac))
1483

    
1484
    self._CheckUniqueUUID(instance, include_temporary=False)
1485

    
1486
    instance.serial_no = 1
1487
    instance.ctime = instance.mtime = time.time()
1488
    self._config_data.instances[instance.uuid] = instance
1489
    self._config_data.cluster.serial_no += 1
1490
    self._UnlockedReleaseDRBDMinors(instance.uuid)
1491
    self._UnlockedCommitTemporaryIps(ec_id)
1492
    self._WriteConfig()
1493

    
1494
  def _EnsureUUID(self, item, ec_id):
1495
    """Ensures a given object has a valid UUID.
1496

1497
    @param item: the instance or node to be checked
1498
    @param ec_id: the execution context id for the uuid reservation
1499

1500
    """
1501
    if not item.uuid:
1502
      item.uuid = self._GenerateUniqueID(ec_id)
1503
    else:
1504
      self._CheckUniqueUUID(item, include_temporary=True)
1505

    
1506
  def _CheckUniqueUUID(self, item, include_temporary):
1507
    """Checks that the UUID of the given object is unique.
1508

1509
    @param item: the instance or node to be checked
1510
    @param include_temporary: whether temporarily generated UUID's should be
1511
              included in the check. If the UUID of the item to be checked is
1512
              a temporarily generated one, this has to be C{False}.
1513

1514
    """
1515
    if not item.uuid:
1516
      raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1517
    if item.uuid in self._AllIDs(include_temporary=include_temporary):
1518
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1519
                                      " in use" % (item.name, item.uuid))
1520

    
1521
  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1522
    """Set the instance's status to a given value.
1523

1524
    """
1525
    if inst_uuid not in self._config_data.instances:
1526
      raise errors.ConfigurationError("Unknown instance '%s'" %
1527
                                      inst_uuid)
1528
    instance = self._config_data.instances[inst_uuid]
1529

    
1530
    if status is None:
1531
      status = instance.admin_state
1532
    if disks_active is None:
1533
      disks_active = instance.disks_active
1534

    
1535
    assert status in constants.ADMINST_ALL, \
1536
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1537

    
1538
    if instance.admin_state != status or \
1539
       instance.disks_active != disks_active:
1540
      instance.admin_state = status
1541
      instance.disks_active = disks_active
1542
      instance.serial_no += 1
1543
      instance.mtime = time.time()
1544
      self._WriteConfig()
1545

    
1546
  @locking.ssynchronized(_config_lock)
1547
  def MarkInstanceUp(self, inst_uuid):
1548
    """Mark the instance status to up in the config.
1549

1550
    This also sets the instance disks active flag.
1551

1552
    """
1553
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1554

    
1555
  @locking.ssynchronized(_config_lock)
1556
  def MarkInstanceOffline(self, inst_uuid):
1557
    """Mark the instance status to down in the config.
1558

1559
    This also clears the instance disks active flag.
1560

1561
    """
1562
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1563

    
1564
  @locking.ssynchronized(_config_lock)
1565
  def RemoveInstance(self, inst_uuid):
1566
    """Remove the instance from the configuration.
1567

1568
    """
1569
    if inst_uuid not in self._config_data.instances:
1570
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1571

    
1572
    # If a network port has been allocated to the instance,
1573
    # return it to the pool of free ports.
1574
    inst = self._config_data.instances[inst_uuid]
1575
    network_port = getattr(inst, "network_port", None)
1576
    if network_port is not None:
1577
      self._config_data.cluster.tcpudp_port_pool.add(network_port)
1578

    
1579
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1580

    
1581
    for nic in instance.nics:
1582
      if nic.network and nic.ip:
1583
        # Return all IP addresses to the respective address pools
1584
        self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1585

    
1586
    del self._config_data.instances[inst_uuid]
1587
    self._config_data.cluster.serial_no += 1
1588
    self._WriteConfig()
1589

    
1590
  @locking.ssynchronized(_config_lock)
1591
  def RenameInstance(self, inst_uuid, new_name):
1592
    """Rename an instance.
1593

1594
    This needs to be done in ConfigWriter and not by RemoveInstance
1595
    combined with AddInstance as only we can guarantee an atomic
1596
    rename.
1597

1598
    """
1599
    if inst_uuid not in self._config_data.instances:
1600
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1601

    
1602
    inst = self._config_data.instances[inst_uuid]
1603
    inst.name = new_name
1604

    
1605
    for (_, disk) in enumerate(inst.disks):
1606
      if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1607
        # rename the file paths in logical and physical id
1608
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1609
        disk.logical_id = (disk.logical_id[0],
1610
                           utils.PathJoin(file_storage_dir, inst.name,
1611
                                          os.path.basename(disk.logical_id[1])))
1612

    
1613
    # Force update of ssconf files
1614
    self._config_data.cluster.serial_no += 1
1615

    
1616
    self._WriteConfig()
1617

    
1618
  @locking.ssynchronized(_config_lock)
1619
  def MarkInstanceDown(self, inst_uuid):
1620
    """Mark the status of an instance to down in the configuration.
1621

1622
    This does not touch the instance disks active flag, as shut down instances
1623
    can still have active disks.
1624

1625
    """
1626
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1627

    
1628
  @locking.ssynchronized(_config_lock)
1629
  def MarkInstanceDisksActive(self, inst_uuid):
1630
    """Mark the status of instance disks active.
1631

1632
    """
1633
    self._SetInstanceStatus(inst_uuid, None, True)
1634

    
1635
  @locking.ssynchronized(_config_lock)
1636
  def MarkInstanceDisksInactive(self, inst_uuid):
1637
    """Mark the status of instance disks inactive.
1638

1639
    """
1640
    self._SetInstanceStatus(inst_uuid, None, False)
1641

    
1642
  def _UnlockedGetInstanceList(self):
1643
    """Get the list of instances.
1644

1645
    This function is for internal use, when the config lock is already held.
1646

1647
    """
1648
    return self._config_data.instances.keys()
1649

    
1650
  @locking.ssynchronized(_config_lock, shared=1)
1651
  def GetInstanceList(self):
1652
    """Get the list of instances.
1653

1654
    @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid']
1655

1656
    """
1657
    return self._UnlockedGetInstanceList()
1658

    
1659
  def ExpandInstanceName(self, short_name):
1660
    """Attempt to expand an incomplete instance name.
1661

1662
    """
1663
    # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1664
    all_insts = self.GetAllInstancesInfo().values()
1665
    expanded_name = _MatchNameComponentIgnoreCase(
1666
                      short_name, [inst.name for inst in all_insts])
1667

    
1668
    if expanded_name is not None:
1669
      # there has to be exactly one instance with that name
1670
      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1671
      return (inst.uuid, inst.name)
1672
    else:
1673
      return (None, None)
1674

    
1675
  def _UnlockedGetInstanceInfo(self, inst_uuid):
1676
    """Returns information about an instance.
1677

1678
    This function is for internal use, when the config lock is already held.
1679

1680
    """
1681
    if inst_uuid not in self._config_data.instances:
1682
      return None
1683

    
1684
    return self._config_data.instances[inst_uuid]
1685

    
1686
  @locking.ssynchronized(_config_lock, shared=1)
1687
  def GetInstanceInfo(self, inst_uuid):
1688
    """Returns information about an instance.
1689

1690
    It takes the information from the configuration file. Other information of
1691
    an instance are taken from the live systems.
1692

1693
    @param inst_uuid: UUID of the instance
1694

1695
    @rtype: L{objects.Instance}
1696
    @return: the instance object
1697

1698
    """
1699
    return self._UnlockedGetInstanceInfo(inst_uuid)
1700

    
1701
  @locking.ssynchronized(_config_lock, shared=1)
1702
  def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1703
    """Returns set of node group UUIDs for instance's nodes.
1704

1705
    @rtype: frozenset
1706

1707
    """
1708
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1709
    if not instance:
1710
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1711

    
1712
    if primary_only:
1713
      nodes = [instance.primary_node]
1714
    else:
1715
      nodes = instance.all_nodes
1716

    
1717
    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1718
                     for node_uuid in nodes)
1719

    
1720
  @locking.ssynchronized(_config_lock, shared=1)
1721
  def GetInstanceNetworks(self, inst_uuid):
1722
    """Returns set of network UUIDs for instance's nics.
1723

1724
    @rtype: frozenset
1725

1726
    """
1727
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1728
    if not instance:
1729
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1730

    
1731
    networks = set()
1732
    for nic in instance.nics:
1733
      if nic.network:
1734
        networks.add(nic.network)
1735

    
1736
    return frozenset(networks)
1737

    
1738
  @locking.ssynchronized(_config_lock, shared=1)
1739
  def GetMultiInstanceInfo(self, inst_uuids):
1740
    """Get the configuration of multiple instances.
1741

1742
    @param inst_uuids: list of instance UUIDs
1743
    @rtype: list
1744
    @return: list of tuples (instance UUID, instance_info), where
1745
        instance_info is what would GetInstanceInfo return for the
1746
        node, while keeping the original order
1747

1748
    """
1749
    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1750

    
1751
  @locking.ssynchronized(_config_lock, shared=1)
1752
  def GetMultiInstanceInfoByName(self, inst_names):
1753
    """Get the configuration of multiple instances.
1754

1755
    @param inst_names: list of instance names
1756
    @rtype: list
1757
    @return: list of tuples (instance, instance_info), where
1758
        instance_info is what would GetInstanceInfo return for the
1759
        node, while keeping the original order
1760

1761
    """
1762
    result = []
1763
    for name in inst_names:
1764
      instance = self._UnlockedGetInstanceInfoByName(name)
1765
      result.append((instance.uuid, instance))
1766
    return result
1767

    
1768
  @locking.ssynchronized(_config_lock, shared=1)
1769
  def GetAllInstancesInfo(self):
1770
    """Get the configuration of all instances.
1771

1772
    @rtype: dict
1773
    @return: dict of (instance, instance_info), where instance_info is what
1774
              would GetInstanceInfo return for the node
1775

1776
    """
1777
    return self._UnlockedGetAllInstancesInfo()
1778

    
1779
  def _UnlockedGetAllInstancesInfo(self):
1780
    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1781
                    for inst_uuid in self._UnlockedGetInstanceList()])
1782
    return my_dict
1783

    
1784
  @locking.ssynchronized(_config_lock, shared=1)
1785
  def GetInstancesInfoByFilter(self, filter_fn):
1786
    """Get instance configuration with a filter.
1787

1788
    @type filter_fn: callable
1789
    @param filter_fn: Filter function receiving instance object as parameter,
1790
      returning boolean. Important: this function is called while the
1791
      configuration locks is held. It must not do any complex work or call
1792
      functions potentially leading to a deadlock. Ideally it doesn't call any
1793
      other functions and just compares instance attributes.
1794

1795
    """
1796
    return dict((uuid, inst)
1797
                for (uuid, inst) in self._config_data.instances.items()
1798
                if filter_fn(inst))
1799

    
1800
  @locking.ssynchronized(_config_lock, shared=1)
1801
  def GetInstanceInfoByName(self, inst_name):
1802
    """Get the L{objects.Instance} object for a named instance.
1803

1804
    @param inst_name: name of the instance to get information for
1805
    @type inst_name: string
1806
    @return: the corresponding L{objects.Instance} instance or None if no
1807
          information is available
1808

1809
    """
1810
    return self._UnlockedGetInstanceInfoByName(inst_name)
1811

    
1812
  def _UnlockedGetInstanceInfoByName(self, inst_name):
1813
    for inst in self._UnlockedGetAllInstancesInfo().values():
1814
      if inst.name == inst_name:
1815
        return inst
1816
    return None
1817

    
1818
  def _UnlockedGetInstanceName(self, inst_uuid):
1819
    inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1820
    if inst_info is None:
1821
      raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1822
    return inst_info.name
1823

    
1824
  @locking.ssynchronized(_config_lock, shared=1)
1825
  def GetInstanceName(self, inst_uuid):
1826
    """Gets the instance name for the passed instance.
1827

1828
    @param inst_uuid: instance UUID to get name for
1829
    @type inst_uuid: string
1830
    @rtype: string
1831
    @return: instance name
1832

1833
    """
1834
    return self._UnlockedGetInstanceName(inst_uuid)
1835

    
1836
  @locking.ssynchronized(_config_lock, shared=1)
1837
  def GetInstanceNames(self, inst_uuids):
1838
    """Gets the instance names for the passed list of nodes.
1839

1840
    @param inst_uuids: list of instance UUIDs to get names for
1841
    @type inst_uuids: list of strings
1842
    @rtype: list of strings
1843
    @return: list of instance names
1844

1845
    """
1846
    return self._UnlockedGetInstanceNames(inst_uuids)
1847

    
1848
  def _UnlockedGetInstanceNames(self, inst_uuids):
1849
    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1850

    
1851
  @locking.ssynchronized(_config_lock)
1852
  def AddNode(self, node, ec_id):
1853
    """Add a node to the configuration.
1854

1855
    @type node: L{objects.Node}
1856
    @param node: a Node instance
1857

1858
    """
1859
    logging.info("Adding node %s to configuration", node.name)
1860

    
1861
    self._EnsureUUID(node, ec_id)
1862

    
1863
    node.serial_no = 1
1864
    node.ctime = node.mtime = time.time()
1865
    self._UnlockedAddNodeToGroup(node.uuid, node.group)
1866
    self._config_data.nodes[node.uuid] = node
1867
    self._config_data.cluster.serial_no += 1
1868
    self._WriteConfig()
1869

    
1870
  @locking.ssynchronized(_config_lock)
1871
  def RemoveNode(self, node_uuid):
1872
    """Remove a node from the configuration.
1873

1874
    """
1875
    logging.info("Removing node %s from configuration", node_uuid)
1876

    
1877
    if node_uuid not in self._config_data.nodes:
1878
      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1879

    
1880
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1881
    del self._config_data.nodes[node_uuid]
1882
    self._config_data.cluster.serial_no += 1
1883
    self._WriteConfig()
1884

    
1885
  def ExpandNodeName(self, short_name):
1886
    """Attempt to expand an incomplete node name into a node UUID.
1887

1888
    """
1889
    # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1890
    all_nodes = self.GetAllNodesInfo().values()
1891
    expanded_name = _MatchNameComponentIgnoreCase(
1892
                      short_name, [node.name for node in all_nodes])
1893

    
1894
    if expanded_name is not None:
1895
      # there has to be exactly one node with that name
1896
      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1897
      return (node.uuid, node.name)
1898
    else:
1899
      return (None, None)
1900

    
1901
  def _UnlockedGetNodeInfo(self, node_uuid):
1902
    """Get the configuration of a node, as stored in the config.
1903

1904
    This function is for internal use, when the config lock is already
1905
    held.
1906

1907
    @param node_uuid: the node UUID
1908

1909
    @rtype: L{objects.Node}
1910
    @return: the node object
1911

1912
    """
1913
    if node_uuid not in self._config_data.nodes:
1914
      return None
1915

    
1916
    return self._config_data.nodes[node_uuid]
1917

    
1918
  @locking.ssynchronized(_config_lock, shared=1)
1919
  def GetNodeInfo(self, node_uuid):
1920
    """Get the configuration of a node, as stored in the config.
1921

1922
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1923

1924
    @param node_uuid: the node UUID
1925

1926
    @rtype: L{objects.Node}
1927
    @return: the node object
1928

1929
    """
1930
    return self._UnlockedGetNodeInfo(node_uuid)
1931

    
1932
  @locking.ssynchronized(_config_lock, shared=1)
1933
  def GetNodeInstances(self, node_uuid):
1934
    """Get the instances of a node, as stored in the config.
1935

1936
    @param node_uuid: the node UUID
1937

1938
    @rtype: (list, list)
1939
    @return: a tuple with two lists: the primary and the secondary instances
1940

1941
    """
1942
    pri = []
1943
    sec = []
1944
    for inst in self._config_data.instances.values():
1945
      if inst.primary_node == node_uuid:
1946
        pri.append(inst.uuid)
1947
      if node_uuid in inst.secondary_nodes:
1948
        sec.append(inst.uuid)
1949
    return (pri, sec)
1950

    
1951
  @locking.ssynchronized(_config_lock, shared=1)
1952
  def GetNodeGroupInstances(self, uuid, primary_only=False):
1953
    """Get the instances of a node group.
1954

1955
    @param uuid: Node group UUID
1956
    @param primary_only: Whether to only consider primary nodes
1957
    @rtype: frozenset
1958
    @return: List of instance UUIDs in node group
1959

1960
    """
1961
    if primary_only:
1962
      nodes_fn = lambda inst: [inst.primary_node]
1963
    else:
1964
      nodes_fn = lambda inst: inst.all_nodes
1965

    
1966
    return frozenset(inst.uuid
1967
                     for inst in self._config_data.instances.values()
1968
                     for node_uuid in nodes_fn(inst)
1969
                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1970

    
1971
  def _UnlockedGetHvparamsString(self, hvname):
1972
    """Return the string representation of the list of hyervisor parameters of
1973
    the given hypervisor.
1974

1975
    @see: C{GetHvparams}
1976

1977
    """
1978
    result = ""
1979
    hvparams = self._config_data.cluster.hvparams[hvname]
1980
    for key in hvparams:
1981
      result += "%s=%s\n" % (key, hvparams[key])
1982
    return result
1983

    
1984
  @locking.ssynchronized(_config_lock, shared=1)
1985
  def GetHvparamsString(self, hvname):
1986
    """Return the hypervisor parameters of the given hypervisor.
1987

1988
    @type hvname: string
1989
    @param hvname: name of a hypervisor
1990
    @rtype: string
1991
    @return: string containing key-value-pairs, one pair on each line;
1992
      format: KEY=VALUE
1993

1994
    """
1995
    return self._UnlockedGetHvparamsString(hvname)
1996

    
1997
  def _UnlockedGetNodeList(self):
1998
    """Return the list of nodes which are in the configuration.
1999

2000
    This function is for internal use, when the config lock is already
2001
    held.
2002

2003
    @rtype: list
2004

2005
    """
2006
    return self._config_data.nodes.keys()
2007

    
2008
  @locking.ssynchronized(_config_lock, shared=1)
2009
  def GetNodeList(self):
2010
    """Return the list of nodes which are in the configuration.
2011

2012
    """
2013
    return self._UnlockedGetNodeList()
2014

    
2015
  def _UnlockedGetOnlineNodeList(self):
2016
    """Return the list of nodes which are online.
2017

2018
    """
2019
    all_nodes = [self._UnlockedGetNodeInfo(node)
2020
                 for node in self._UnlockedGetNodeList()]
2021
    return [node.uuid for node in all_nodes if not node.offline]
2022

    
2023
  @locking.ssynchronized(_config_lock, shared=1)
2024
  def GetOnlineNodeList(self):
2025
    """Return the list of nodes which are online.
2026

2027
    """
2028
    return self._UnlockedGetOnlineNodeList()
2029

    
2030
  @locking.ssynchronized(_config_lock, shared=1)
2031
  def GetVmCapableNodeList(self):
2032
    """Return the list of nodes which are not vm capable.
2033

2034
    """
2035
    all_nodes = [self._UnlockedGetNodeInfo(node)
2036
                 for node in self._UnlockedGetNodeList()]
2037
    return [node.uuid for node in all_nodes if node.vm_capable]
2038

    
2039
  @locking.ssynchronized(_config_lock, shared=1)
2040
  def GetNonVmCapableNodeList(self):
2041
    """Return the list of nodes which are not vm capable.
2042

2043
    """
2044
    all_nodes = [self._UnlockedGetNodeInfo(node)
2045
                 for node in self._UnlockedGetNodeList()]
2046
    return [node.uuid for node in all_nodes if not node.vm_capable]
2047

    
2048
  @locking.ssynchronized(_config_lock, shared=1)
2049
  def GetMultiNodeInfo(self, node_uuids):
2050
    """Get the configuration of multiple nodes.
2051

2052
    @param node_uuids: list of node UUIDs
2053
    @rtype: list
2054
    @return: list of tuples of (node, node_info), where node_info is
2055
        what would GetNodeInfo return for the node, in the original
2056
        order
2057

2058
    """
2059
    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
2060

    
2061
  def _UnlockedGetAllNodesInfo(self):
2062
    """Gets configuration of all nodes.
2063

2064
    @note: See L{GetAllNodesInfo}
2065

2066
    """
2067
    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
2068
                 for node_uuid in self._UnlockedGetNodeList()])
2069

    
2070
  @locking.ssynchronized(_config_lock, shared=1)
2071
  def GetAllNodesInfo(self):
2072
    """Get the configuration of all nodes.
2073

2074
    @rtype: dict
2075
    @return: dict of (node, node_info), where node_info is what
2076
              would GetNodeInfo return for the node
2077

2078
    """
2079
    return self._UnlockedGetAllNodesInfo()
2080

    
2081
  def _UnlockedGetNodeInfoByName(self, node_name):
2082
    for node in self._UnlockedGetAllNodesInfo().values():
2083
      if node.name == node_name:
2084
        return node
2085
    return None
2086

    
2087
  @locking.ssynchronized(_config_lock, shared=1)
2088
  def GetNodeInfoByName(self, node_name):
2089
    """Get the L{objects.Node} object for a named node.
2090

2091
    @param node_name: name of the node to get information for
2092
    @type node_name: string
2093
    @return: the corresponding L{objects.Node} instance or None if no
2094
          information is available
2095

2096
    """
2097
    return self._UnlockedGetNodeInfoByName(node_name)
2098

    
2099
  @locking.ssynchronized(_config_lock, shared=1)
2100
  def GetNodeGroupInfoByName(self, nodegroup_name):
2101
    """Get the L{objects.NodeGroup} object for a named node group.
2102

2103
    @param nodegroup_name: name of the node group to get information for
2104
    @type nodegroup_name: string
2105
    @return: the corresponding L{objects.NodeGroup} instance or None if no
2106
          information is available
2107

2108
    """
2109
    for nodegroup in self._UnlockedGetAllNodeGroupsInfo().values():
2110
      if nodegroup.name == nodegroup_name:
2111
        return nodegroup
2112
    return None
2113

    
2114
  def _UnlockedGetNodeName(self, node_spec):
2115
    if isinstance(node_spec, objects.Node):
2116
      return node_spec.name
2117
    elif isinstance(node_spec, basestring):
2118
      node_info = self._UnlockedGetNodeInfo(node_spec)
2119
      if node_info is None:
2120
        raise errors.OpExecError("Unknown node: %s" % node_spec)
2121
      return node_info.name
2122
    else:
2123
      raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2124

    
2125
  @locking.ssynchronized(_config_lock, shared=1)
2126
  def GetNodeName(self, node_spec):
2127
    """Gets the node name for the passed node.
2128

2129
    @param node_spec: node to get names for
2130
    @type node_spec: either node UUID or a L{objects.Node} object
2131
    @rtype: string
2132
    @return: node name
2133

2134
    """
2135
    return self._UnlockedGetNodeName(node_spec)
2136

    
2137
  def _UnlockedGetNodeNames(self, node_specs):
2138
    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2139

    
2140
  @locking.ssynchronized(_config_lock, shared=1)
2141
  def GetNodeNames(self, node_specs):
2142
    """Gets the node names for the passed list of nodes.
2143

2144
    @param node_specs: list of nodes to get names for
2145
    @type node_specs: list of either node UUIDs or L{objects.Node} objects
2146
    @rtype: list of strings
2147
    @return: list of node names
2148

2149
    """
2150
    return self._UnlockedGetNodeNames(node_specs)
2151

    
2152
  @locking.ssynchronized(_config_lock, shared=1)
2153
  def GetNodeGroupsFromNodes(self, node_uuids):
2154
    """Returns groups for a list of nodes.
2155

2156
    @type node_uuids: list of string
2157
    @param node_uuids: List of node UUIDs
2158
    @rtype: frozenset
2159

2160
    """
2161
    return frozenset(self._UnlockedGetNodeInfo(uuid).group
2162
                     for uuid in node_uuids)
2163

    
2164
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2165
    """Get the number of current and maximum desired and possible candidates.
2166

2167
    @type exceptions: list
2168
    @param exceptions: if passed, list of nodes that should be ignored
2169
    @rtype: tuple
2170
    @return: tuple of (current, desired and possible, possible)
2171

2172
    """
2173
    mc_now = mc_should = mc_max = 0
2174
    for node in self._config_data.nodes.values():
2175
      if exceptions and node.uuid in exceptions:
2176
        continue
2177
      if not (node.offline or node.drained) and node.master_capable:
2178
        mc_max += 1
2179
      if node.master_candidate:
2180
        mc_now += 1
2181
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2182
    return (mc_now, mc_should, mc_max)
2183

    
2184
  @locking.ssynchronized(_config_lock, shared=1)
2185
  def GetMasterCandidateStats(self, exceptions=None):
2186
    """Get the number of current and maximum possible candidates.
2187

2188
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2189

2190
    @type exceptions: list
2191
    @param exceptions: if passed, list of nodes that should be ignored
2192
    @rtype: tuple
2193
    @return: tuple of (current, max)
2194

2195
    """
2196
    return self._UnlockedGetMasterCandidateStats(exceptions)
2197

    
2198
  @locking.ssynchronized(_config_lock)
2199
  def MaintainCandidatePool(self, exception_node_uuids):
2200
    """Try to grow the candidate pool to the desired size.
2201

2202
    @type exception_node_uuids: list
2203
    @param exception_node_uuids: if passed, list of nodes that should be ignored
2204
    @rtype: list
2205
    @return: list with the adjusted nodes (L{objects.Node} instances)
2206

2207
    """
2208
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2209
                          exception_node_uuids)
2210
    mod_list = []
2211
    if mc_now < mc_max:
2212
      node_list = self._config_data.nodes.keys()
2213
      random.shuffle(node_list)
2214
      for uuid in node_list:
2215
        if mc_now >= mc_max:
2216
          break
2217
        node = self._config_data.nodes[uuid]
2218
        if (node.master_candidate or node.offline or node.drained or
2219
            node.uuid in exception_node_uuids or not node.master_capable):
2220
          continue
2221
        mod_list.append(node)
2222
        node.master_candidate = True
2223
        node.serial_no += 1
2224
        mc_now += 1
2225
      if mc_now != mc_max:
2226
        # this should not happen
2227
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
2228
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
2229
      if mod_list:
2230
        self._config_data.cluster.serial_no += 1
2231
        self._WriteConfig()
2232

    
2233
    return mod_list
2234

    
2235
  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2236
    """Add a given node to the specified group.
2237

2238
    """
2239
    if nodegroup_uuid not in self._config_data.nodegroups:
2240
      # This can happen if a node group gets deleted between its lookup and
2241
      # when we're adding the first node to it, since we don't keep a lock in
2242
      # the meantime. It's ok though, as we'll fail cleanly if the node group
2243
      # is not found anymore.
2244
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2245
    if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2246
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2247

    
2248
  def _UnlockedRemoveNodeFromGroup(self, node):
2249
    """Remove a given node from its group.
2250

2251
    """
2252
    nodegroup = node.group
2253
    if nodegroup not in self._config_data.nodegroups:
2254
      logging.warning("Warning: node '%s' has unknown node group '%s'"
2255
                      " (while being removed from it)", node.uuid, nodegroup)
2256
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
2257
    if node.uuid not in nodegroup_obj.members:
2258
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
2259
                      " (while being removed from it)", node.uuid, nodegroup)
2260
    else:
2261
      nodegroup_obj.members.remove(node.uuid)
2262

    
2263
  @locking.ssynchronized(_config_lock)
2264
  def AssignGroupNodes(self, mods):
2265
    """Changes the group of a number of nodes.
2266

2267
    @type mods: list of tuples; (node name, new group UUID)
2268
    @param mods: Node membership modifications
2269

2270
    """
2271
    groups = self._config_data.nodegroups
2272
    nodes = self._config_data.nodes
2273

    
2274
    resmod = []
2275

    
2276
    # Try to resolve UUIDs first
2277
    for (node_uuid, new_group_uuid) in mods:
2278
      try:
2279
        node = nodes[node_uuid]
2280
      except KeyError:
2281
        raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2282

    
2283
      if node.group == new_group_uuid:
2284
        # Node is being assigned to its current group
2285
        logging.debug("Node '%s' was assigned to its current group (%s)",
2286
                      node_uuid, node.group)
2287
        continue
2288

    
2289
      # Try to find current group of node
2290
      try:
2291
        old_group = groups[node.group]
2292
      except KeyError:
2293
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2294
                                        node.group)
2295

    
2296
      # Try to find new group for node
2297
      try:
2298
        new_group = groups[new_group_uuid]
2299
      except KeyError:
2300
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2301
                                        new_group_uuid)
2302

    
2303
      assert node.uuid in old_group.members, \
2304
        ("Inconsistent configuration: node '%s' not listed in members for its"
2305
         " old group '%s'" % (node.uuid, old_group.uuid))
2306
      assert node.uuid not in new_group.members, \
2307
        ("Inconsistent configuration: node '%s' already listed in members for"
2308
         " its new group '%s'" % (node.uuid, new_group.uuid))
2309

    
2310
      resmod.append((node, old_group, new_group))
2311

    
2312
    # Apply changes
2313
    for (node, old_group, new_group) in resmod:
2314
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2315
        "Assigning to current group is not possible"
2316

    
2317
      node.group = new_group.uuid
2318

    
2319
      # Update members of involved groups
2320
      if node.uuid in old_group.members:
2321
        old_group.members.remove(node.uuid)
2322
      if node.uuid not in new_group.members:
2323
        new_group.members.append(node.uuid)
2324

    
2325
    # Update timestamps and serials (only once per node/group object)
2326
    now = time.time()
2327
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2328
      obj.serial_no += 1
2329
      obj.mtime = now
2330

    
2331
    # Force ssconf update
2332
    self._config_data.cluster.serial_no += 1
2333

    
2334
    self._WriteConfig()
2335

    
2336
  def _BumpSerialNo(self):
2337
    """Bump up the serial number of the config.
2338

2339
    """
2340
    self._config_data.serial_no += 1
2341
    self._config_data.mtime = time.time()
2342

    
2343
  def _AllUUIDObjects(self):
2344
    """Returns all objects with uuid attributes.
2345

2346
    """
2347
    return (self._config_data.instances.values() +
2348
            self._config_data.nodes.values() +
2349
            self._config_data.nodegroups.values() +
2350
            self._config_data.networks.values() +
2351
            self._AllDisks() +
2352
            self._AllNICs() +
2353
            [self._config_data.cluster])
2354

    
2355
  def _OpenConfig(self, accept_foreign):
2356
    """Read the config data from disk.
2357

2358
    """
2359
    # Read the configuration data. If offline, read the file directly.
2360
    # If online, call WConfd.
2361
    if self._offline:
2362
      raw_data = utils.ReadFile(self._cfg_file)
2363
      try:
2364
        dict_data = serializer.Load(raw_data)
2365
      except Exception, err:
2366
        raise errors.ConfigurationError(err)
2367
    else:
2368
      self._wconfd = wc.Client()
2369
      dict_data = self._wconfd.ReadConfig()
2370

    
2371
    try:
2372
      data = objects.ConfigData.FromDict(dict_data)
2373
    except Exception, err:
2374
      raise errors.ConfigurationError(err)
2375

    
2376
    # Make sure the configuration has the right version
2377
    _ValidateConfig(data)
2378

    
2379
    if (not hasattr(data, "cluster") or
2380
        not hasattr(data.cluster, "rsahostkeypub")):
2381
      raise errors.ConfigurationError("Incomplete configuration"
2382
                                      " (missing cluster.rsahostkeypub)")
2383

    
2384
    if not data.cluster.master_node in data.nodes:
2385
      msg = ("The configuration denotes node %s as master, but does not"
2386
             " contain information about this node" %
2387
             data.cluster.master_node)
2388
      raise errors.ConfigurationError(msg)
2389

    
2390
    master_info = data.nodes[data.cluster.master_node]
2391
    if master_info.name != self._my_hostname and not accept_foreign:
2392
      msg = ("The configuration denotes node %s as master, while my"
2393
             " hostname is %s; opening a foreign configuration is only"
2394
             " possible in accept_foreign mode" %
2395
             (master_info.name, self._my_hostname))
2396
      raise errors.ConfigurationError(msg)
2397

    
2398
    self._config_data = data
2399

    
2400
    # Upgrade configuration if needed
2401
    self._UpgradeConfig()
2402

    
2403
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2404

    
2405
  def _UpgradeConfig(self):
2406
    """Run any upgrade steps.
2407

2408
    This method performs both in-object upgrades and also update some data
2409
    elements that need uniqueness across the whole configuration or interact
2410
    with other objects.
2411

2412
    @warning: this function will call L{_WriteConfig()}, but also
2413
        L{DropECReservations} so it needs to be called only from a
2414
        "safe" place (the constructor). If one wanted to call it with
2415
        the lock held, a DropECReservationUnlocked would need to be
2416
        created first, to avoid causing deadlock.
2417

2418
    """
2419
    # Keep a copy of the persistent part of _config_data to check for changes
2420
    # Serialization doesn't guarantee order in dictionaries
2421
    oldconf = copy.deepcopy(self._config_data.ToDict())
2422

    
2423
    # In-object upgrades
2424
    self._config_data.UpgradeConfig()
2425

    
2426
    for item in self._AllUUIDObjects():
2427
      if item.uuid is None:
2428
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2429
    if not self._config_data.nodegroups:
2430
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2431
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2432
                                            members=[])
2433
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2434
    for node in self._config_data.nodes.values():
2435
      if not node.group:
2436
        node.group = self.LookupNodeGroup(None)
2437
      # This is technically *not* an upgrade, but needs to be done both when
2438
      # nodegroups are being added, and upon normally loading the config,
2439
      # because the members list of a node group is discarded upon
2440
      # serializing/deserializing the object.
2441
      self._UnlockedAddNodeToGroup(node.uuid, node.group)
2442

    
2443
    modified = (oldconf != self._config_data.ToDict())
2444
    if modified:
2445
      self._WriteConfig()
2446
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2447
      # only called at config init time, without the lock held
2448
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2449
    else:
2450
      config_errors = self._UnlockedVerifyConfig()
2451
      if config_errors:
2452
        errmsg = ("Loaded configuration data is not consistent: %s" %
2453
                  (utils.CommaJoin(config_errors)))
2454
        logging.critical(errmsg)
2455

    
2456
  def _WriteConfig(self, destination=None, feedback_fn=None):
2457
    """Write the configuration data to persistent storage.
2458

2459
    """
2460
    assert feedback_fn is None or callable(feedback_fn)
2461

    
2462
    # Warn on config errors, but don't abort the save - the
2463
    # configuration has already been modified, and we can't revert;
2464
    # the best we can do is to warn the user and save as is, leaving
2465
    # recovery to the user
2466
    config_errors = self._UnlockedVerifyConfig()
2467
    if config_errors:
2468
      errmsg = ("Configuration data is not consistent: %s" %
2469
                (utils.CommaJoin(config_errors)))
2470
      logging.critical(errmsg)
2471
      if feedback_fn:
2472
        feedback_fn(errmsg)
2473

    
2474
    if destination is None:
2475
      destination = self._cfg_file
2476

    
2477
    self._BumpSerialNo()
2478
    # Save the configuration data. If offline, write the file directly.
2479
    # If online, call WConfd.
2480
    if self._offline:
2481
      txt = serializer.DumpJson(
2482
        self._config_data.ToDict(_with_private=True),
2483
        private_encoder=serializer.EncodeWithPrivateFields
2484
      )
2485

    
2486
      getents = self._getents()
2487
      try:
2488
        fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2489
                                 close=False, gid=getents.confd_gid, mode=0640)
2490
      except errors.LockError:
2491
        raise errors.ConfigurationError("The configuration file has been"
2492
                                        " modified since the last write, cannot"
2493
                                        " update")
2494
      try:
2495
        self._cfg_id = utils.GetFileID(fd=fd)
2496
      finally:
2497
        os.close(fd)
2498
    else:
2499
      try:
2500
        self._wconfd.WriteConfig(self._config_data.ToDict())
2501
      except errors.LockError:
2502
        raise errors.ConfigurationError("The configuration file has been"
2503
                                        " modified since the last write, cannot"
2504
                                        " update")
2505

    
2506
    self.write_count += 1
2507

    
2508
  def _GetAllHvparamsStrings(self, hypervisors):
2509
    """Get the hvparams of all given hypervisors from the config.
2510

2511
    @type hypervisors: list of string
2512
    @param hypervisors: list of hypervisor names
2513
    @rtype: dict of strings
2514
    @returns: dictionary mapping the hypervisor name to a string representation
2515
      of the hypervisor's hvparams
2516

2517
    """
2518
    hvparams = {}
2519
    for hv in hypervisors:
2520
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2521
    return hvparams
2522

    
2523
  @staticmethod
2524
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2525
    """Extends the ssconf_values dictionary by hvparams.
2526

2527
    @type ssconf_values: dict of strings
2528
    @param ssconf_values: dictionary mapping ssconf_keys to strings
2529
      representing the content of ssconf files
2530
    @type all_hvparams: dict of strings
2531
    @param all_hvparams: dictionary mapping hypervisor names to a string
2532
      representation of their hvparams
2533
    @rtype: same as ssconf_values
2534
    @returns: the ssconf_values dictionary extended by hvparams
2535

2536
    """
2537
    for hv in all_hvparams:
2538
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2539
      ssconf_values[ssconf_key] = all_hvparams[hv]
2540
    return ssconf_values
2541

    
2542
  def _UnlockedGetSsconfValues(self):
2543
    """Return the values needed by ssconf.
2544

2545
    @rtype: dict
2546
    @return: a dictionary with keys the ssconf names and values their
2547
        associated value
2548

2549
    """
2550
    fn = "\n".join
2551
    instance_names = utils.NiceSort(
2552
                       [inst.name for inst in
2553
                        self._UnlockedGetAllInstancesInfo().values()])
2554
    node_infos = self._UnlockedGetAllNodesInfo().values()
2555
    node_names = [node.name for node in node_infos]
2556
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2557
                    for ninfo in node_infos]
2558
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2559
                    for ninfo in node_infos]
2560

    
2561
    instance_data = fn(instance_names)
2562
    off_data = fn(node.name for node in node_infos if node.offline)
2563
    on_data = fn(node.name for node in node_infos if not node.offline)
2564
    mc_data = fn(node.name for node in node_infos if node.master_candidate)
2565
    mc_ips_data = fn(node.primary_ip for node in node_infos
2566
                     if node.master_candidate)
2567
    node_data = fn(node_names)
2568
    node_pri_ips_data = fn(node_pri_ips)
2569
    node_snd_ips_data = fn(node_snd_ips)
2570

    
2571
    cluster = self._config_data.cluster
2572
    cluster_tags = fn(cluster.GetTags())
2573

    
2574
    master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert)
2575
                                 for mc_uuid, mc_cert
2576
                                 in cluster.candidate_certs.items())
2577

    
2578
    hypervisor_list = fn(cluster.enabled_hypervisors)
2579
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2580

    
2581
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2582

    
2583
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2584
                  self._config_data.nodegroups.values()]
2585
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2586
    networks = ["%s %s" % (net.uuid, net.name) for net in
2587
                self._config_data.networks.values()]
2588
    networks_data = fn(utils.NiceSort(networks))
2589

    
2590
    ssconf_values = {
2591
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
2592
      constants.SS_CLUSTER_TAGS: cluster_tags,
2593
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2594
      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2595
      constants.SS_GLUSTER_STORAGE_DIR: cluster.gluster_storage_dir,
2596
      constants.SS_MASTER_CANDIDATES: mc_data,
2597
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2598
      constants.SS_MASTER_CANDIDATES_CERTS: master_candidates_certs,
2599
      constants.SS_MASTER_IP: cluster.master_ip,
2600
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
2601
      constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2602
      constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2603
      constants.SS_NODE_LIST: node_data,
2604
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2605
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2606
      constants.SS_OFFLINE_NODES: off_data,
2607
      constants.SS_ONLINE_NODES: on_data,
2608
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2609
      constants.SS_INSTANCE_LIST: instance_data,
2610
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2611
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
2612
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2613
      constants.SS_UID_POOL: uid_pool,
2614
      constants.SS_NODEGROUPS: nodegroups_data,
2615
      constants.SS_NETWORKS: networks_data,
2616
      }
2617
    ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2618
                                                     all_hvparams)
2619
    bad_values = [(k, v) for k, v in ssconf_values.items()
2620
                  if not isinstance(v, (str, basestring))]
2621
    if bad_values:
2622
      err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2623
      raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2624
                                      " values: %s" % err)
2625
    return ssconf_values
2626

    
2627
  @locking.ssynchronized(_config_lock, shared=1)
2628
  def GetSsconfValues(self):
2629
    """Wrapper using lock around _UnlockedGetSsconf().
2630

2631
    """
2632
    return self._UnlockedGetSsconfValues()
2633

    
2634
  @locking.ssynchronized(_config_lock, shared=1)
2635
  def GetVGName(self):
2636
    """Return the volume group name.
2637

2638
    """
2639
    return self._config_data.cluster.volume_group_name
2640

    
2641
  @locking.ssynchronized(_config_lock)
2642
  def SetVGName(self, vg_name):
2643
    """Set the volume group name.
2644

2645
    """
2646
    self._config_data.cluster.volume_group_name = vg_name
2647
    self._config_data.cluster.serial_no += 1
2648
    self._WriteConfig()
2649

    
2650
  @locking.ssynchronized(_config_lock, shared=1)
2651
  def GetDRBDHelper(self):
2652
    """Return DRBD usermode helper.
2653

2654
    """
2655
    return self._config_data.cluster.drbd_usermode_helper
2656

    
2657
  @locking.ssynchronized(_config_lock)
2658
  def SetDRBDHelper(self, drbd_helper):
2659
    """Set DRBD usermode helper.
2660

2661
    """
2662
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2663
    self._config_data.cluster.serial_no += 1
2664
    self._WriteConfig()
2665

    
2666
  @locking.ssynchronized(_config_lock, shared=1)
2667
  def GetMACPrefix(self):
2668
    """Return the mac prefix.
2669

2670
    """
2671
    return self._config_data.cluster.mac_prefix
2672

    
2673
  @locking.ssynchronized(_config_lock, shared=1)
2674
  def GetClusterInfo(self):
2675
    """Returns information about the cluster
2676

2677
    @rtype: L{objects.Cluster}
2678
    @return: the cluster object
2679

2680
    """
2681
    return self._config_data.cluster
2682

    
2683
  @locking.ssynchronized(_config_lock, shared=1)
2684
  def HasAnyDiskOfType(self, dev_type):
2685
    """Check if in there is at disk of the given type in the configuration.
2686

2687
    """
2688
    return self._config_data.HasAnyDiskOfType(dev_type)
2689

    
2690
  @locking.ssynchronized(_config_lock)
2691
  def Update(self, target, feedback_fn, ec_id=None):
2692
    """Notify function to be called after updates.
2693

2694
    This function must be called when an object (as returned by
2695
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2696
    caller wants the modifications saved to the backing store. Note
2697
    that all modified objects will be saved, but the target argument
2698
    is the one the caller wants to ensure that it's saved.
2699

2700
    @param target: an instance of either L{objects.Cluster},
2701
        L{objects.Node} or L{objects.Instance} which is existing in
2702
        the cluster
2703
    @param feedback_fn: Callable feedback function
2704

2705
    """
2706
    if self._config_data is None:
2707
      raise errors.ProgrammerError("Configuration file not read,"
2708
                                   " cannot save.")
2709
    update_serial = False
2710
    if isinstance(target, objects.Cluster):
2711
      test = target == self._config_data.cluster
2712
    elif isinstance(target, objects.Node):
2713
      test = target in self._config_data.nodes.values()
2714
      update_serial = True
2715
    elif isinstance(target, objects.Instance):
2716
      test = target in self._config_data.instances.values()
2717
    elif isinstance(target, objects.NodeGroup):
2718
      test = target in self._config_data.nodegroups.values()
2719
    elif isinstance(target, objects.Network):
2720
      test = target in self._config_data.networks.values()
2721
    else:
2722
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2723
                                   " ConfigWriter.Update" % type(target))
2724
    if not test:
2725
      raise errors.ConfigurationError("Configuration updated since object"
2726
                                      " has been read or unknown object")
2727
    target.serial_no += 1
2728
    target.mtime = now = time.time()
2729

    
2730
    if update_serial:
2731
      # for node updates, we need to increase the cluster serial too
2732
      self._config_data.cluster.serial_no += 1
2733
      self._config_data.cluster.mtime = now
2734

    
2735
    if isinstance(target, objects.Instance):
2736
      self._UnlockedReleaseDRBDMinors(target.uuid)
2737

    
2738
    if ec_id is not None:
2739
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2740
      self._UnlockedCommitTemporaryIps(ec_id)
2741

    
2742
    self._WriteConfig(feedback_fn=feedback_fn)
2743

    
2744
  @locking.ssynchronized(_config_lock)
2745
  def DropECReservations(self, ec_id):
2746
    """Drop per-execution-context reservations
2747

2748
    """
2749
    for rm in self._all_rms:
2750
      rm.DropECReservations(ec_id)
2751

    
2752
  @locking.ssynchronized(_config_lock, shared=1)
2753
  def GetAllNetworksInfo(self):
2754
    """Get configuration info of all the networks.
2755

2756
    """
2757
    return dict(self._config_data.networks)
2758

    
2759
  def _UnlockedGetNetworkList(self):
2760
    """Get the list of networks.
2761

2762
    This function is for internal use, when the config lock is already held.
2763

2764
    """
2765
    return self._config_data.networks.keys()
2766

    
2767
  @locking.ssynchronized(_config_lock, shared=1)
2768
  def GetNetworkList(self):
2769
    """Get the list of networks.
2770

2771
    @return: array of networks, ex. ["main", "vlan100", "200]
2772

2773
    """
2774
    return self._UnlockedGetNetworkList()
2775

    
2776
  @locking.ssynchronized(_config_lock, shared=1)
2777
  def GetNetworkNames(self):
2778
    """Get a list of network names
2779

2780
    """
2781
    names = [net.name
2782
             for net in self._config_data.networks.values()]
2783
    return names
2784

    
2785
  def _UnlockedGetNetwork(self, uuid):
2786
    """Returns information about a network.
2787

2788
    This function is for internal use, when the config lock is already held.
2789

2790
    """
2791
    if uuid not in self._config_data.networks:
2792
      return None
2793

    
2794
    return self._config_data.networks[uuid]
2795

    
2796
  @locking.ssynchronized(_config_lock, shared=1)
2797
  def GetNetwork(self, uuid):
2798
    """Returns information about a network.
2799

2800
    It takes the information from the configuration file.
2801

2802
    @param uuid: UUID of the network
2803

2804
    @rtype: L{objects.Network}
2805
    @return: the network object
2806

2807
    """
2808
    return self._UnlockedGetNetwork(uuid)
2809

    
2810
  @locking.ssynchronized(_config_lock)
2811
  def AddNetwork(self, net, ec_id, check_uuid=True):
2812
    """Add a network to the configuration.
2813

2814
    @type net: L{objects.Network}
2815
    @param net: the Network object to add
2816
    @type ec_id: string
2817
    @param ec_id: unique id for the job to use when creating a missing UUID
2818

2819
    """
2820
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2821
    self._WriteConfig()
2822

    
2823
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2824
    """Add a network to the configuration.
2825

2826
    """
2827
    logging.info("Adding network %s to configuration", net.name)
2828

    
2829
    if check_uuid:
2830
      self._EnsureUUID(net, ec_id)
2831

    
2832
    net.serial_no = 1
2833
    net.ctime = net.mtime = time.time()
2834
    self._config_data.networks[net.uuid] = net
2835
    self._config_data.cluster.serial_no += 1
2836

    
2837
  def _UnlockedLookupNetwork(self, target):
2838
    """Lookup a network's UUID.
2839

2840
    @type target: string
2841
    @param target: network name or UUID
2842
    @rtype: string
2843
    @return: network UUID
2844
    @raises errors.OpPrereqError: when the target network cannot be found
2845

2846
    """
2847
    if target is None:
2848
      return None
2849
    if target in self._config_data.networks:
2850
      return target
2851
    for net in self._config_data.networks.values():
2852
      if net.name == target:
2853
        return net.uuid
2854
    raise errors.OpPrereqError("Network '%s' not found" % target,
2855
                               errors.ECODE_NOENT)
2856

    
2857
  @locking.ssynchronized(_config_lock, shared=1)
2858
  def LookupNetwork(self, target):
2859
    """Lookup a network's UUID.
2860

2861
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2862

2863
    @type target: string
2864
    @param target: network name or UUID
2865
    @rtype: string
2866
    @return: network UUID
2867

2868
    """
2869
    return self._UnlockedLookupNetwork(target)
2870

    
2871
  @locking.ssynchronized(_config_lock)
2872
  def RemoveNetwork(self, network_uuid):
2873
    """Remove a network from the configuration.
2874

2875
    @type network_uuid: string
2876
    @param network_uuid: the UUID of the network to remove
2877

2878
    """
2879
    logging.info("Removing network %s from configuration", network_uuid)
2880

    
2881
    if network_uuid not in self._config_data.networks:
2882
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2883

    
2884
    del self._config_data.networks[network_uuid]
2885
    self._config_data.cluster.serial_no += 1
2886
    self._WriteConfig()
2887

    
2888
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2889
    """Get the netparams (mode, link) of a network.
2890

2891
    Get a network's netparams for a given node.
2892

2893
    @type net_uuid: string
2894
    @param net_uuid: network uuid
2895
    @type node_uuid: string
2896
    @param node_uuid: node UUID
2897
    @rtype: dict or None
2898
    @return: netparams
2899

2900
    """
2901
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2902
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2903
    netparams = nodegroup_info.networks.get(net_uuid, None)
2904

    
2905
    return netparams
2906

    
2907
  @locking.ssynchronized(_config_lock, shared=1)
2908
  def GetGroupNetParams(self, net_uuid, node_uuid):
2909
    """Locking wrapper of _UnlockedGetGroupNetParams()
2910

2911
    """
2912
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2913

    
2914
  @locking.ssynchronized(_config_lock, shared=1)
2915
  def CheckIPInNodeGroup(self, ip, node_uuid):
2916
    """Check IP uniqueness in nodegroup.
2917

2918
    Check networks that are connected in the node's node group
2919
    if ip is contained in any of them. Used when creating/adding
2920
    a NIC to ensure uniqueness among nodegroups.
2921

2922
    @type ip: string
2923
    @param ip: ip address
2924
    @type node_uuid: string
2925
    @param node_uuid: node UUID
2926
    @rtype: (string, dict) or (None, None)
2927
    @return: (network name, netparams)
2928

2929
    """
2930
    if ip is None:
2931
      return (None, None)
2932
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2933
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2934
    for net_uuid in nodegroup_info.networks.keys():
2935
      net_info = self._UnlockedGetNetwork(net_uuid)
2936
      pool = network.AddressPool(net_info)
2937
      if pool.Contains(ip):
2938
        return (net_info.name, nodegroup_info.networks[net_uuid])
2939

    
2940
    return (None, None)