Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ d3e6fd0e

History | View | Annotate | Download (96.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Configuration management for Ganeti
23

24
This module provides the interface to the Ganeti cluster configuration.
25

26
The configuration data is stored on every node but is updated on the master
27
only. After each update, the master distributes the data to the other nodes.
28

29
Currently, the data storage format is JSON. YAML was slow and consuming too
30
much memory.
31

32
"""
33

    
34
# pylint: disable=R0904
35
# R0904: Too many public methods
36

    
37
import copy
38
import os
39
import random
40
import logging
41
import time
42
import itertools
43

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

    
57

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

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

    
63

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

67
  This only verifies the version of the configuration.
68

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

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

    
76

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

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

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

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

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

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

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

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

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

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

126
    """
127
    assert callable(generate_one_fn)
128

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

    
142

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

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

    
149

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

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

159
  """
160
  result = []
161

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

    
167
  return result
168

    
169

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

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

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

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

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

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

216
    """
217
    self._context = context
218

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
294
    return prefix
295

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

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

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

    
312
    return GenMac
313

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

318
    This should check the current instances for duplicates.
319

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

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

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

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

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

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

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

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

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

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

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

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

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

374
    This is just a wrapper around _UnlockedReleaseIp.
375

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

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

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

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

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

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

401
    """
402
    nobj = self._UnlockedGetNetwork(net_uuid)
403
    pool = network.AddressPool(nobj)
404
    try:
405
      isreserved = pool.IsReserved(address)
406
    except errors.AddressPoolError:
407
      raise errors.ReservationError("IP address not in network")
408
    if isreserved:
409
      raise errors.ReservationError("IP address already in use")
410

    
411
    return self._temporary_ips.Reserve(ec_id,
412
                                       (constants.RESERVE_ACTION,
413
                                        address, net_uuid))
414

    
415
  @locking.ssynchronized(_config_lock, shared=1)
416
  def ReserveIp(self, net_uuid, address, ec_id):
417
    """Reserve a given IPv4 address for use by an instance.
418

419
    """
420
    if net_uuid:
421
      return self._UnlockedReserveIp(net_uuid, address, ec_id)
422

    
423
  @locking.ssynchronized(_config_lock, shared=1)
424
  def ReserveLV(self, lv_name, ec_id):
425
    """Reserve an VG/LV pair for an instance.
426

427
    @type lv_name: string
428
    @param lv_name: the logical volume name to reserve
429

430
    """
431
    all_lvs = self._AllLVs()
432
    if lv_name in all_lvs:
433
      raise errors.ReservationError("LV already in use")
434
    else:
435
      self._temporary_lvs.Reserve(ec_id, lv_name)
436

    
437
  @locking.ssynchronized(_config_lock, shared=1)
438
  def GenerateDRBDSecret(self, ec_id):
439
    """Generate a DRBD secret.
440

441
    This checks the current disks for duplicates.
442

443
    """
444
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
445
                                            utils.GenerateSecret,
446
                                            ec_id)
447

    
448
  def _AllLVs(self):
449
    """Compute the list of all LVs.
450

451
    """
452
    lvnames = set()
453
    for instance in self._config_data.instances.values():
454
      node_data = instance.MapLVsByNode()
455
      for lv_list in node_data.values():
456
        lvnames.update(lv_list)
457
    return lvnames
458

    
459
  def _AllDisks(self):
460
    """Compute the list of all Disks (recursively, including children).
461

462
    """
463
    def DiskAndAllChildren(disk):
464
      """Returns a list containing the given disk and all of his children.
465

466
      """
467
      disks = [disk]
468
      if disk.children:
469
        for child_disk in disk.children:
470
          disks.extend(DiskAndAllChildren(child_disk))
471
      return disks
472

    
473
    disks = []
474
    for instance in self._config_data.instances.values():
475
      for disk in instance.disks:
476
        disks.extend(DiskAndAllChildren(disk))
477
    return disks
478

    
479
  def _AllNICs(self):
480
    """Compute the list of all NICs.
481

482
    """
483
    nics = []
484
    for instance in self._config_data.instances.values():
485
      nics.extend(instance.nics)
486
    return nics
487

    
488
  def _AllIDs(self, include_temporary):
489
    """Compute the list of all UUIDs and names we have.
490

491
    @type include_temporary: boolean
492
    @param include_temporary: whether to include the _temporary_ids set
493
    @rtype: set
494
    @return: a set of IDs
495

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

    
506
  def _GenerateUniqueID(self, ec_id):
507
    """Generate an unique UUID.
508

509
    This checks the current node, instances and disk names for
510
    duplicates.
511

512
    @rtype: string
513
    @return: the unique id
514

515
    """
516
    existing = self._AllIDs(include_temporary=False)
517
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
518

    
519
  @locking.ssynchronized(_config_lock, shared=1)
520
  def GenerateUniqueID(self, ec_id):
521
    """Generate an unique ID.
522

523
    This is just a wrapper over the unlocked version.
524

525
    @type ec_id: string
526
    @param ec_id: unique id for the job to reserve the id to
527

528
    """
529
    return self._GenerateUniqueID(ec_id)
530

    
531
  def _AllMACs(self):
532
    """Return all MACs present in the config.
533

534
    @rtype: list
535
    @return: the list of all MACs
536

537
    """
538
    result = []
539
    for instance in self._config_data.instances.values():
540
      for nic in instance.nics:
541
        result.append(nic.mac)
542

    
543
    return result
544

    
545
  def _AllDRBDSecrets(self):
546
    """Return all DRBD secrets present in the config.
547

548
    @rtype: list
549
    @return: the list of all DRBD secrets
550

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

    
560
    result = []
561
    for instance in self._config_data.instances.values():
562
      for disk in instance.disks:
563
        helper(disk, result)
564

    
565
    return result
566

    
567
  def _CheckDiskIDs(self, disk, l_ids):
568
    """Compute duplicate disk IDs
569

570
    @type disk: L{objects.Disk}
571
    @param disk: the disk at which to start searching
572
    @type l_ids: list
573
    @param l_ids: list of current logical ids
574
    @rtype: list
575
    @return: a list of error messages
576

577
    """
578
    result = []
579
    if disk.logical_id is not None:
580
      if disk.logical_id in l_ids:
581
        result.append("duplicate logical id %s" % str(disk.logical_id))
582
      else:
583
        l_ids.append(disk.logical_id)
584

    
585
    if disk.children:
586
      for child in disk.children:
587
        result.extend(self._CheckDiskIDs(child, l_ids))
588
    return result
589

    
590
  def _UnlockedVerifyConfig(self):
591
    """Verify function.
592

593
    @rtype: list
594
    @return: a list of error messages; a non-empty list signifies
595
        configuration errors
596

597
    """
598
    # pylint: disable=R0914
599
    result = []
600
    seen_macs = []
601
    ports = {}
602
    data = self._config_data
603
    cluster = data.cluster
604
    seen_lids = []
605

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

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

    
627
    if cluster.master_node not in data.nodes:
628
      result.append("cluster has invalid primary node '%s'" %
629
                    cluster.master_node)
630

    
631
    def _helper(owner, attr, value, template):
632
      try:
633
        utils.ForceDictType(value, template)
634
      except errors.GenericError, err:
635
        result.append("%s has invalid %s: %s" % (owner, attr, err))
636

    
637
    def _helper_nic(owner, params):
638
      try:
639
        objects.NIC.CheckParameterSyntax(params)
640
      except errors.ConfigurationError, err:
641
        result.append("%s has invalid nicparams: %s" % (owner, err))
642

    
643
    def _helper_ipolicy(owner, ipolicy, iscluster):
644
      try:
645
        objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
646
      except errors.ConfigurationError, err:
647
        result.append("%s has invalid instance policy: %s" % (owner, err))
648
      for key, value in ipolicy.items():
649
        if key == constants.ISPECS_MINMAX:
650
          for k in range(len(value)):
651
            _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
652
        elif key == constants.ISPECS_STD:
653
          _helper(owner, "ipolicy/" + key, value,
654
                  constants.ISPECS_PARAMETER_TYPES)
655
        else:
656
          # FIXME: assuming list type
657
          if key in constants.IPOLICY_PARAMETERS:
658
            exp_type = float
659
          else:
660
            exp_type = list
661
          if not isinstance(value, exp_type):
662
            result.append("%s has invalid instance policy: for %s,"
663
                          " expecting %s, got %s" %
664
                          (owner, key, exp_type.__name__, type(value)))
665

    
666
    def _helper_ispecs(owner, parentkey, params):
667
      for (key, value) in params.items():
668
        fullkey = "/".join([parentkey, key])
669
        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
670

    
671
    # check cluster parameters
672
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
673
            constants.BES_PARAMETER_TYPES)
674
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
675
            constants.NICS_PARAMETER_TYPES)
676
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
677
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
678
            constants.NDS_PARAMETER_TYPES)
679
    _helper_ipolicy("cluster", cluster.ipolicy, True)
680

    
681
    if constants.DT_RBD in cluster.diskparams:
682
      access = cluster.diskparams[constants.DT_RBD][constants.RBD_ACCESS]
683
      if access not in constants.DISK_VALID_ACCESS_MODES:
684
        result.append(
685
          "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
686
            constants.DT_RBD, constants.RBD_ACCESS, access,
687
            utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
688
          )
689
        )
690

    
691
    # per-instance checks
692
    for instance_uuid in data.instances:
693
      instance = data.instances[instance_uuid]
694
      if instance.uuid != instance_uuid:
695
        result.append("instance '%s' is indexed by wrong UUID '%s'" %
696
                      (instance.name, instance_uuid))
697
      if instance.primary_node not in data.nodes:
698
        result.append("instance '%s' has invalid primary node '%s'" %
699
                      (instance.name, instance.primary_node))
700
      for snode in instance.secondary_nodes:
701
        if snode not in data.nodes:
702
          result.append("instance '%s' has invalid secondary node '%s'" %
703
                        (instance.name, snode))
704
      for idx, nic in enumerate(instance.nics):
705
        if nic.mac in seen_macs:
706
          result.append("instance '%s' has NIC %d mac %s duplicate" %
707
                        (instance.name, idx, nic.mac))
708
        else:
709
          seen_macs.append(nic.mac)
710
        if nic.nicparams:
711
          filled = cluster.SimpleFillNIC(nic.nicparams)
712
          owner = "instance %s nic %d" % (instance.name, idx)
713
          _helper(owner, "nicparams",
714
                  filled, constants.NICS_PARAMETER_TYPES)
715
          _helper_nic(owner, filled)
716

    
717
      # disk template checks
718
      if not instance.disk_template in data.cluster.enabled_disk_templates:
719
        result.append("instance '%s' uses the disabled disk template '%s'." %
720
                      (instance.name, instance.disk_template))
721

    
722
      # parameter checks
723
      if instance.beparams:
724
        _helper("instance %s" % instance.name, "beparams",
725
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
726

    
727
      # gather the drbd ports for duplicate checks
728
      for (idx, dsk) in enumerate(instance.disks):
729
        if dsk.dev_type in constants.DTS_DRBD:
730
          tcp_port = dsk.logical_id[2]
731
          if tcp_port not in ports:
732
            ports[tcp_port] = []
733
          ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
734
      # gather network port reservation
735
      net_port = getattr(instance, "network_port", None)
736
      if net_port is not None:
737
        if net_port not in ports:
738
          ports[net_port] = []
739
        ports[net_port].append((instance.name, "network port"))
740

    
741
      # instance disk verify
742
      for idx, disk in enumerate(instance.disks):
743
        result.extend(["instance '%s' disk %d error: %s" %
744
                       (instance.name, idx, msg) for msg in disk.Verify()])
745
        result.extend(self._CheckDiskIDs(disk, seen_lids))
746

    
747
      wrong_names = _CheckInstanceDiskIvNames(instance.disks)
748
      if wrong_names:
749
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
750
                         (idx, exp_name, actual_name))
751
                        for (idx, exp_name, actual_name) in wrong_names)
752

    
753
        result.append("Instance '%s' has wrongly named disks: %s" %
754
                      (instance.name, tmp))
755

    
756
    # cluster-wide pool of free ports
757
    for free_port in cluster.tcpudp_port_pool:
758
      if free_port not in ports:
759
        ports[free_port] = []
760
      ports[free_port].append(("cluster", "port marked as free"))
761

    
762
    # compute tcp/udp duplicate ports
763
    keys = ports.keys()
764
    keys.sort()
765
    for pnum in keys:
766
      pdata = ports[pnum]
767
      if len(pdata) > 1:
768
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
769
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
770

    
771
    # highest used tcp port check
772
    if keys:
773
      if keys[-1] > cluster.highest_used_port:
774
        result.append("Highest used port mismatch, saved %s, computed %s" %
775
                      (cluster.highest_used_port, keys[-1]))
776

    
777
    if not data.nodes[cluster.master_node].master_candidate:
778
      result.append("Master node is not a master candidate")
779

    
780
    # master candidate checks
781
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
782
    if mc_now < mc_max:
783
      result.append("Not enough master candidates: actual %d, target %d" %
784
                    (mc_now, mc_max))
785

    
786
    # node checks
787
    for node_uuid, node in data.nodes.items():
788
      if node.uuid != node_uuid:
789
        result.append("Node '%s' is indexed by wrong UUID '%s'" %
790
                      (node.name, node_uuid))
791
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
792
        result.append("Node %s state is invalid: master_candidate=%s,"
793
                      " drain=%s, offline=%s" %
794
                      (node.name, node.master_candidate, node.drained,
795
                       node.offline))
796
      if node.group not in data.nodegroups:
797
        result.append("Node '%s' has invalid group '%s'" %
798
                      (node.name, node.group))
799
      else:
800
        _helper("node %s" % node.name, "ndparams",
801
                cluster.FillND(node, data.nodegroups[node.group]),
802
                constants.NDS_PARAMETER_TYPES)
803
      used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
804
      if used_globals:
805
        result.append("Node '%s' has some global parameters set: %s" %
806
                      (node.name, utils.CommaJoin(used_globals)))
807

    
808
    # nodegroups checks
809
    nodegroups_names = set()
810
    for nodegroup_uuid in data.nodegroups:
811
      nodegroup = data.nodegroups[nodegroup_uuid]
812
      if nodegroup.uuid != nodegroup_uuid:
813
        result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
814
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
815
      if utils.UUID_RE.match(nodegroup.name.lower()):
816
        result.append("node group '%s' (uuid: '%s') has uuid-like name" %
817
                      (nodegroup.name, nodegroup.uuid))
818
      if nodegroup.name in nodegroups_names:
819
        result.append("duplicate node group name '%s'" % nodegroup.name)
820
      else:
821
        nodegroups_names.add(nodegroup.name)
822
      group_name = "group %s" % nodegroup.name
823
      _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
824
                      False)
825
      if nodegroup.ndparams:
826
        _helper(group_name, "ndparams",
827
                cluster.SimpleFillND(nodegroup.ndparams),
828
                constants.NDS_PARAMETER_TYPES)
829

    
830
    # drbd minors check
831
    _, duplicates = self._UnlockedComputeDRBDMap()
832
    for node, minor, instance_a, instance_b in duplicates:
833
      result.append("DRBD minor %d on node %s is assigned twice to instances"
834
                    " %s and %s" % (minor, node, instance_a, instance_b))
835

    
836
    # IP checks
837
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
838
    ips = {}
839

    
840
    def _AddIpAddress(ip, name):
841
      ips.setdefault(ip, []).append(name)
842

    
843
    _AddIpAddress(cluster.master_ip, "cluster_ip")
844

    
845
    for node in data.nodes.values():
846
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
847
      if node.secondary_ip != node.primary_ip:
848
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
849

    
850
    for instance in data.instances.values():
851
      for idx, nic in enumerate(instance.nics):
852
        if nic.ip is None:
853
          continue
854

    
855
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
856
        nic_mode = nicparams[constants.NIC_MODE]
857
        nic_link = nicparams[constants.NIC_LINK]
858

    
859
        if nic_mode == constants.NIC_MODE_BRIDGED:
860
          link = "bridge:%s" % nic_link
861
        elif nic_mode == constants.NIC_MODE_ROUTED:
862
          link = "route:%s" % nic_link
863
        else:
864
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
865

    
866
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
867
                      "instance:%s/nic:%d" % (instance.name, idx))
868

    
869
    for ip, owners in ips.items():
870
      if len(owners) > 1:
871
        result.append("IP address %s is used by multiple owners: %s" %
872
                      (ip, utils.CommaJoin(owners)))
873

    
874
    return result
875

    
876
  @locking.ssynchronized(_config_lock, shared=1)
877
  def VerifyConfig(self):
878
    """Verify function.
879

880
    This is just a wrapper over L{_UnlockedVerifyConfig}.
881

882
    @rtype: list
883
    @return: a list of error messages; a non-empty list signifies
884
        configuration errors
885

886
    """
887
    return self._UnlockedVerifyConfig()
888

    
889
  @locking.ssynchronized(_config_lock)
890
  def AddTcpUdpPort(self, port):
891
    """Adds a new port to the available port pool.
892

893
    @warning: this method does not "flush" the configuration (via
894
        L{_WriteConfig}); callers should do that themselves once the
895
        configuration is stable
896

897
    """
898
    if not isinstance(port, int):
899
      raise errors.ProgrammerError("Invalid type passed for port")
900

    
901
    self._config_data.cluster.tcpudp_port_pool.add(port)
902

    
903
  @locking.ssynchronized(_config_lock, shared=1)
904
  def GetPortList(self):
905
    """Returns a copy of the current port list.
906

907
    """
908
    return self._config_data.cluster.tcpudp_port_pool.copy()
909

    
910
  @locking.ssynchronized(_config_lock)
911
  def AllocatePort(self):
912
    """Allocate a port.
913

914
    The port will be taken from the available port pool or from the
915
    default port range (and in this case we increase
916
    highest_used_port).
917

918
    """
919
    # If there are TCP/IP ports configured, we use them first.
920
    if self._config_data.cluster.tcpudp_port_pool:
921
      port = self._config_data.cluster.tcpudp_port_pool.pop()
922
    else:
923
      port = self._config_data.cluster.highest_used_port + 1
924
      if port >= constants.LAST_DRBD_PORT:
925
        raise errors.ConfigurationError("The highest used port is greater"
926
                                        " than %s. Aborting." %
927
                                        constants.LAST_DRBD_PORT)
928
      self._config_data.cluster.highest_used_port = port
929

    
930
    self._WriteConfig()
931
    return port
932

    
933
  def _UnlockedComputeDRBDMap(self):
934
    """Compute the used DRBD minor/nodes.
935

936
    @rtype: (dict, list)
937
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
938
        the returned dict will have all the nodes in it (even if with
939
        an empty list), and a list of duplicates; if the duplicates
940
        list is not empty, the configuration is corrupted and its caller
941
        should raise an exception
942

943
    """
944
    def _AppendUsedMinors(get_node_name_fn, instance, disk, used):
945
      duplicates = []
946
      if disk.dev_type == constants.DT_DRBD8 and len(disk.logical_id) >= 5:
947
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
948
        for node_uuid, minor in ((node_a, minor_a), (node_b, minor_b)):
949
          assert node_uuid in used, \
950
            ("Node '%s' of instance '%s' not found in node list" %
951
             (get_node_name_fn(node_uuid), instance.name))
952
          if minor in used[node_uuid]:
953
            duplicates.append((node_uuid, minor, instance.uuid,
954
                               used[node_uuid][minor]))
955
          else:
956
            used[node_uuid][minor] = instance.uuid
957
      if disk.children:
958
        for child in disk.children:
959
          duplicates.extend(_AppendUsedMinors(get_node_name_fn, instance, child,
960
                                              used))
961
      return duplicates
962

    
963
    duplicates = []
964
    my_dict = dict((node_uuid, {}) for node_uuid in self._config_data.nodes)
965
    for instance in self._config_data.instances.itervalues():
966
      for disk in instance.disks:
967
        duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
968
                                            instance, disk, my_dict))
969
    for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
970
      if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
971
        duplicates.append((node_uuid, minor, inst_uuid,
972
                           my_dict[node_uuid][minor]))
973
      else:
974
        my_dict[node_uuid][minor] = inst_uuid
975
    return my_dict, duplicates
976

    
977
  @locking.ssynchronized(_config_lock)
978
  def ComputeDRBDMap(self):
979
    """Compute the used DRBD minor/nodes.
980

981
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
982

983
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
984
        the returned dict will have all the nodes in it (even if with
985
        an empty list).
986

987
    """
988
    d_map, duplicates = self._UnlockedComputeDRBDMap()
989
    if duplicates:
990
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
991
                                      str(duplicates))
992
    return d_map
993

    
994
  @locking.ssynchronized(_config_lock)
995
  def AllocateDRBDMinor(self, node_uuids, inst_uuid):
996
    """Allocate a drbd minor.
997

998
    The free minor will be automatically computed from the existing
999
    devices. A node can be given multiple times in order to allocate
1000
    multiple minors. The result is the list of minors, in the same
1001
    order as the passed nodes.
1002

1003
    @type inst_uuid: string
1004
    @param inst_uuid: the instance for which we allocate minors
1005

1006
    """
1007
    assert isinstance(inst_uuid, basestring), \
1008
           "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
1009

    
1010
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1011
    if duplicates:
1012
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1013
                                      str(duplicates))
1014
    result = []
1015
    for nuuid in node_uuids:
1016
      ndata = d_map[nuuid]
1017
      if not ndata:
1018
        # no minors used, we can start at 0
1019
        result.append(0)
1020
        ndata[0] = inst_uuid
1021
        self._temporary_drbds[(nuuid, 0)] = inst_uuid
1022
        continue
1023
      keys = ndata.keys()
1024
      keys.sort()
1025
      ffree = utils.FirstFree(keys)
1026
      if ffree is None:
1027
        # return the next minor
1028
        # TODO: implement high-limit check
1029
        minor = keys[-1] + 1
1030
      else:
1031
        minor = ffree
1032
      # double-check minor against current instances
1033
      assert minor not in d_map[nuuid], \
1034
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
1035
              " already allocated to instance %s" %
1036
              (minor, nuuid, d_map[nuuid][minor]))
1037
      ndata[minor] = inst_uuid
1038
      # double-check minor against reservation
1039
      r_key = (nuuid, minor)
1040
      assert r_key not in self._temporary_drbds, \
1041
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
1042
              " reserved for instance %s" %
1043
              (minor, nuuid, self._temporary_drbds[r_key]))
1044
      self._temporary_drbds[r_key] = inst_uuid
1045
      result.append(minor)
1046
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1047
                  node_uuids, result)
1048
    return result
1049

    
1050
  def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1051
    """Release temporary drbd minors allocated for a given instance.
1052

1053
    @type inst_uuid: string
1054
    @param inst_uuid: the instance for which temporary minors should be
1055
                      released
1056

1057
    """
1058
    assert isinstance(inst_uuid, basestring), \
1059
           "Invalid argument passed to ReleaseDRBDMinors"
1060
    for key, uuid in self._temporary_drbds.items():
1061
      if uuid == inst_uuid:
1062
        del self._temporary_drbds[key]
1063

    
1064
  @locking.ssynchronized(_config_lock)
1065
  def ReleaseDRBDMinors(self, inst_uuid):
1066
    """Release temporary drbd minors allocated for a given instance.
1067

1068
    This should be called on the error paths, on the success paths
1069
    it's automatically called by the ConfigWriter add and update
1070
    functions.
1071

1072
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1073

1074
    @type inst_uuid: string
1075
    @param inst_uuid: the instance for which temporary minors should be
1076
                      released
1077

1078
    """
1079
    self._UnlockedReleaseDRBDMinors(inst_uuid)
1080

    
1081
  @locking.ssynchronized(_config_lock, shared=1)
1082
  def GetConfigVersion(self):
1083
    """Get the configuration version.
1084

1085
    @return: Config version
1086

1087
    """
1088
    return self._config_data.version
1089

    
1090
  @locking.ssynchronized(_config_lock, shared=1)
1091
  def GetClusterName(self):
1092
    """Get cluster name.
1093

1094
    @return: Cluster name
1095

1096
    """
1097
    return self._config_data.cluster.cluster_name
1098

    
1099
  @locking.ssynchronized(_config_lock, shared=1)
1100
  def GetMasterNode(self):
1101
    """Get the UUID of the master node for this cluster.
1102

1103
    @return: Master node UUID
1104

1105
    """
1106
    return self._config_data.cluster.master_node
1107

    
1108
  @locking.ssynchronized(_config_lock, shared=1)
1109
  def GetMasterNodeName(self):
1110
    """Get the hostname of the master node for this cluster.
1111

1112
    @return: Master node hostname
1113

1114
    """
1115
    return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
1116

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

1121
    @rtype: objects.Node
1122
    @return: Master node L{objects.Node} object
1123

1124
    """
1125
    return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
1126

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

1131
    @return: Master IP
1132

1133
    """
1134
    return self._config_data.cluster.master_ip
1135

    
1136
  @locking.ssynchronized(_config_lock, shared=1)
1137
  def GetMasterNetdev(self):
1138
    """Get the master network device for this cluster.
1139

1140
    """
1141
    return self._config_data.cluster.master_netdev
1142

    
1143
  @locking.ssynchronized(_config_lock, shared=1)
1144
  def GetMasterNetmask(self):
1145
    """Get the netmask of the master node for this cluster.
1146

1147
    """
1148
    return self._config_data.cluster.master_netmask
1149

    
1150
  @locking.ssynchronized(_config_lock, shared=1)
1151
  def GetUseExternalMipScript(self):
1152
    """Get flag representing whether to use the external master IP setup script.
1153

1154
    """
1155
    return self._config_data.cluster.use_external_mip_script
1156

    
1157
  @locking.ssynchronized(_config_lock, shared=1)
1158
  def GetFileStorageDir(self):
1159
    """Get the file storage dir for this cluster.
1160

1161
    """
1162
    return self._config_data.cluster.file_storage_dir
1163

    
1164
  @locking.ssynchronized(_config_lock, shared=1)
1165
  def GetSharedFileStorageDir(self):
1166
    """Get the shared file storage dir for this cluster.
1167

1168
    """
1169
    return self._config_data.cluster.shared_file_storage_dir
1170

    
1171
  @locking.ssynchronized(_config_lock, shared=1)
1172
  def GetGlusterStorageDir(self):
1173
    """Get the Gluster storage dir for this cluster.
1174

1175
    """
1176
    return self._config_data.cluster.gluster_storage_dir
1177

    
1178
  @locking.ssynchronized(_config_lock, shared=1)
1179
  def GetHypervisorType(self):
1180
    """Get the hypervisor type for this cluster.
1181

1182
    """
1183
    return self._config_data.cluster.enabled_hypervisors[0]
1184

    
1185
  @locking.ssynchronized(_config_lock, shared=1)
1186
  def GetRsaHostKey(self):
1187
    """Return the rsa hostkey from the config.
1188

1189
    @rtype: string
1190
    @return: the rsa hostkey
1191

1192
    """
1193
    return self._config_data.cluster.rsahostkeypub
1194

    
1195
  @locking.ssynchronized(_config_lock, shared=1)
1196
  def GetDsaHostKey(self):
1197
    """Return the dsa hostkey from the config.
1198

1199
    @rtype: string
1200
    @return: the dsa hostkey
1201

1202
    """
1203
    return self._config_data.cluster.dsahostkeypub
1204

    
1205
  @locking.ssynchronized(_config_lock, shared=1)
1206
  def GetDefaultIAllocator(self):
1207
    """Get the default instance allocator for this cluster.
1208

1209
    """
1210
    return self._config_data.cluster.default_iallocator
1211

    
1212
  @locking.ssynchronized(_config_lock, shared=1)
1213
  def GetDefaultIAllocatorParameters(self):
1214
    """Get the default instance allocator parameters for this cluster.
1215

1216
    @rtype: dict
1217
    @return: dict of iallocator parameters
1218

1219
    """
1220
    return self._config_data.cluster.default_iallocator_params
1221

    
1222
  @locking.ssynchronized(_config_lock, shared=1)
1223
  def GetPrimaryIPFamily(self):
1224
    """Get cluster primary ip family.
1225

1226
    @return: primary ip family
1227

1228
    """
1229
    return self._config_data.cluster.primary_ip_family
1230

    
1231
  @locking.ssynchronized(_config_lock, shared=1)
1232
  def GetMasterNetworkParameters(self):
1233
    """Get network parameters of the master node.
1234

1235
    @rtype: L{object.MasterNetworkParameters}
1236
    @return: network parameters of the master node
1237

1238
    """
1239
    cluster = self._config_data.cluster
1240
    result = objects.MasterNetworkParameters(
1241
      uuid=cluster.master_node, ip=cluster.master_ip,
1242
      netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1243
      ip_family=cluster.primary_ip_family)
1244

    
1245
    return result
1246

    
1247
  @locking.ssynchronized(_config_lock)
1248
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1249
    """Add a node group to the configuration.
1250

1251
    This method calls group.UpgradeConfig() to fill any missing attributes
1252
    according to their default values.
1253

1254
    @type group: L{objects.NodeGroup}
1255
    @param group: the NodeGroup object to add
1256
    @type ec_id: string
1257
    @param ec_id: unique id for the job to use when creating a missing UUID
1258
    @type check_uuid: bool
1259
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1260
                       it does, ensure that it does not exist in the
1261
                       configuration already
1262

1263
    """
1264
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1265
    self._WriteConfig()
1266

    
1267
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1268
    """Add a node group to the configuration.
1269

1270
    """
1271
    logging.info("Adding node group %s to configuration", group.name)
1272

    
1273
    # Some code might need to add a node group with a pre-populated UUID
1274
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1275
    # the "does this UUID" exist already check.
1276
    if check_uuid:
1277
      self._EnsureUUID(group, ec_id)
1278

    
1279
    try:
1280
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1281
    except errors.OpPrereqError:
1282
      pass
1283
    else:
1284
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1285
                                 " node group (UUID: %s)" %
1286
                                 (group.name, existing_uuid),
1287
                                 errors.ECODE_EXISTS)
1288

    
1289
    group.serial_no = 1
1290
    group.ctime = group.mtime = time.time()
1291
    group.UpgradeConfig()
1292

    
1293
    self._config_data.nodegroups[group.uuid] = group
1294
    self._config_data.cluster.serial_no += 1
1295

    
1296
  @locking.ssynchronized(_config_lock)
1297
  def RemoveNodeGroup(self, group_uuid):
1298
    """Remove a node group from the configuration.
1299

1300
    @type group_uuid: string
1301
    @param group_uuid: the UUID of the node group to remove
1302

1303
    """
1304
    logging.info("Removing node group %s from configuration", group_uuid)
1305

    
1306
    if group_uuid not in self._config_data.nodegroups:
1307
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1308

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

    
1312
    del self._config_data.nodegroups[group_uuid]
1313
    self._config_data.cluster.serial_no += 1
1314
    self._WriteConfig()
1315

    
1316
  def _UnlockedLookupNodeGroup(self, target):
1317
    """Lookup a node group's UUID.
1318

1319
    @type target: string or None
1320
    @param target: group name or UUID or None to look for the default
1321
    @rtype: string
1322
    @return: nodegroup UUID
1323
    @raises errors.OpPrereqError: when the target group cannot be found
1324

1325
    """
1326
    if target is None:
1327
      if len(self._config_data.nodegroups) != 1:
1328
        raise errors.OpPrereqError("More than one node group exists. Target"
1329
                                   " group must be specified explicitly.")
1330
      else:
1331
        return self._config_data.nodegroups.keys()[0]
1332
    if target in self._config_data.nodegroups:
1333
      return target
1334
    for nodegroup in self._config_data.nodegroups.values():
1335
      if nodegroup.name == target:
1336
        return nodegroup.uuid
1337
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1338
                               errors.ECODE_NOENT)
1339

    
1340
  @locking.ssynchronized(_config_lock, shared=1)
1341
  def LookupNodeGroup(self, target):
1342
    """Lookup a node group's UUID.
1343

1344
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1345

1346
    @type target: string or None
1347
    @param target: group name or UUID or None to look for the default
1348
    @rtype: string
1349
    @return: nodegroup UUID
1350

1351
    """
1352
    return self._UnlockedLookupNodeGroup(target)
1353

    
1354
  def _UnlockedGetNodeGroup(self, uuid):
1355
    """Lookup a node group.
1356

1357
    @type uuid: string
1358
    @param uuid: group UUID
1359
    @rtype: L{objects.NodeGroup} or None
1360
    @return: nodegroup object, or None if not found
1361

1362
    """
1363
    if uuid not in self._config_data.nodegroups:
1364
      return None
1365

    
1366
    return self._config_data.nodegroups[uuid]
1367

    
1368
  @locking.ssynchronized(_config_lock, shared=1)
1369
  def GetNodeGroup(self, uuid):
1370
    """Lookup a node group.
1371

1372
    @type uuid: string
1373
    @param uuid: group UUID
1374
    @rtype: L{objects.NodeGroup} or None
1375
    @return: nodegroup object, or None if not found
1376

1377
    """
1378
    return self._UnlockedGetNodeGroup(uuid)
1379

    
1380
  def _UnlockedGetAllNodeGroupsInfo(self):
1381
    """Get the configuration of all node groups.
1382

1383
    """
1384
    return dict(self._config_data.nodegroups)
1385

    
1386
  @locking.ssynchronized(_config_lock, shared=1)
1387
  def GetAllNodeGroupsInfo(self):
1388
    """Get the configuration of all node groups.
1389

1390
    """
1391
    return self._UnlockedGetAllNodeGroupsInfo()
1392

    
1393
  @locking.ssynchronized(_config_lock, shared=1)
1394
  def GetAllNodeGroupsInfoDict(self):
1395
    """Get the configuration of all node groups expressed as a dictionary of
1396
    dictionaries.
1397

1398
    """
1399
    return dict(map(lambda (uuid, ng): (uuid, ng.ToDict()),
1400
                    self._UnlockedGetAllNodeGroupsInfo().items()))
1401

    
1402
  @locking.ssynchronized(_config_lock, shared=1)
1403
  def GetNodeGroupList(self):
1404
    """Get a list of node groups.
1405

1406
    """
1407
    return self._config_data.nodegroups.keys()
1408

    
1409
  @locking.ssynchronized(_config_lock, shared=1)
1410
  def GetNodeGroupMembersByNodes(self, nodes):
1411
    """Get nodes which are member in the same nodegroups as the given nodes.
1412

1413
    """
1414
    ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1415
    return frozenset(member_uuid
1416
                     for node_uuid in nodes
1417
                     for member_uuid in
1418
                       self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1419

    
1420
  @locking.ssynchronized(_config_lock, shared=1)
1421
  def GetMultiNodeGroupInfo(self, group_uuids):
1422
    """Get the configuration of multiple node groups.
1423

1424
    @param group_uuids: List of node group UUIDs
1425
    @rtype: list
1426
    @return: List of tuples of (group_uuid, group_info)
1427

1428
    """
1429
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1430

    
1431
  @locking.ssynchronized(_config_lock)
1432
  def AddInstance(self, instance, ec_id):
1433
    """Add an instance to the config.
1434

1435
    This should be used after creating a new instance.
1436

1437
    @type instance: L{objects.Instance}
1438
    @param instance: the instance object
1439

1440
    """
1441
    if not isinstance(instance, objects.Instance):
1442
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1443

    
1444
    if instance.disk_template != constants.DT_DISKLESS:
1445
      all_lvs = instance.MapLVsByNode()
1446
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1447

    
1448
    all_macs = self._AllMACs()
1449
    for nic in instance.nics:
1450
      if nic.mac in all_macs:
1451
        raise errors.ConfigurationError("Cannot add instance %s:"
1452
                                        " MAC address '%s' already in use." %
1453
                                        (instance.name, nic.mac))
1454

    
1455
    self._CheckUniqueUUID(instance, include_temporary=False)
1456

    
1457
    instance.serial_no = 1
1458
    instance.ctime = instance.mtime = time.time()
1459
    self._config_data.instances[instance.uuid] = instance
1460
    self._config_data.cluster.serial_no += 1
1461
    self._UnlockedReleaseDRBDMinors(instance.uuid)
1462
    self._UnlockedCommitTemporaryIps(ec_id)
1463
    self._WriteConfig()
1464

    
1465
  def _EnsureUUID(self, item, ec_id):
1466
    """Ensures a given object has a valid UUID.
1467

1468
    @param item: the instance or node to be checked
1469
    @param ec_id: the execution context id for the uuid reservation
1470

1471
    """
1472
    if not item.uuid:
1473
      item.uuid = self._GenerateUniqueID(ec_id)
1474
    else:
1475
      self._CheckUniqueUUID(item, include_temporary=True)
1476

    
1477
  def _CheckUniqueUUID(self, item, include_temporary):
1478
    """Checks that the UUID of the given object is unique.
1479

1480
    @param item: the instance or node to be checked
1481
    @param include_temporary: whether temporarily generated UUID's should be
1482
              included in the check. If the UUID of the item to be checked is
1483
              a temporarily generated one, this has to be C{False}.
1484

1485
    """
1486
    if not item.uuid:
1487
      raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1488
    if item.uuid in self._AllIDs(include_temporary=include_temporary):
1489
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1490
                                      " in use" % (item.name, item.uuid))
1491

    
1492
  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1493
    """Set the instance's status to a given value.
1494

1495
    """
1496
    if inst_uuid not in self._config_data.instances:
1497
      raise errors.ConfigurationError("Unknown instance '%s'" %
1498
                                      inst_uuid)
1499
    instance = self._config_data.instances[inst_uuid]
1500

    
1501
    if status is None:
1502
      status = instance.admin_state
1503
    if disks_active is None:
1504
      disks_active = instance.disks_active
1505

    
1506
    assert status in constants.ADMINST_ALL, \
1507
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1508

    
1509
    if instance.admin_state != status or \
1510
       instance.disks_active != disks_active:
1511
      instance.admin_state = status
1512
      instance.disks_active = disks_active
1513
      instance.serial_no += 1
1514
      instance.mtime = time.time()
1515
      self._WriteConfig()
1516

    
1517
  @locking.ssynchronized(_config_lock)
1518
  def MarkInstanceUp(self, inst_uuid):
1519
    """Mark the instance status to up in the config.
1520

1521
    This also sets the instance disks active flag.
1522

1523
    """
1524
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1525

    
1526
  @locking.ssynchronized(_config_lock)
1527
  def MarkInstanceOffline(self, inst_uuid):
1528
    """Mark the instance status to down in the config.
1529

1530
    This also clears the instance disks active flag.
1531

1532
    """
1533
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1534

    
1535
  @locking.ssynchronized(_config_lock)
1536
  def RemoveInstance(self, inst_uuid):
1537
    """Remove the instance from the configuration.
1538

1539
    """
1540
    if inst_uuid not in self._config_data.instances:
1541
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1542

    
1543
    # If a network port has been allocated to the instance,
1544
    # return it to the pool of free ports.
1545
    inst = self._config_data.instances[inst_uuid]
1546
    network_port = getattr(inst, "network_port", None)
1547
    if network_port is not None:
1548
      self._config_data.cluster.tcpudp_port_pool.add(network_port)
1549

    
1550
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1551

    
1552
    for nic in instance.nics:
1553
      if nic.network and nic.ip:
1554
        # Return all IP addresses to the respective address pools
1555
        self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1556

    
1557
    del self._config_data.instances[inst_uuid]
1558
    self._config_data.cluster.serial_no += 1
1559
    self._WriteConfig()
1560

    
1561
  @locking.ssynchronized(_config_lock)
1562
  def RenameInstance(self, inst_uuid, new_name):
1563
    """Rename an instance.
1564

1565
    This needs to be done in ConfigWriter and not by RemoveInstance
1566
    combined with AddInstance as only we can guarantee an atomic
1567
    rename.
1568

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

    
1573
    inst = self._config_data.instances[inst_uuid]
1574
    inst.name = new_name
1575

    
1576
    for (idx, disk) in enumerate(inst.disks):
1577
      if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1578
        # rename the file paths in logical and physical id
1579
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1580
        disk.logical_id = (disk.logical_id[0],
1581
                           utils.PathJoin(file_storage_dir, inst.name,
1582
                                          "disk%s" % idx))
1583

    
1584
    # Force update of ssconf files
1585
    self._config_data.cluster.serial_no += 1
1586

    
1587
    self._WriteConfig()
1588

    
1589
  @locking.ssynchronized(_config_lock)
1590
  def MarkInstanceDown(self, inst_uuid):
1591
    """Mark the status of an instance to down in the configuration.
1592

1593
    This does not touch the instance disks active flag, as shut down instances
1594
    can still have active disks.
1595

1596
    """
1597
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1598

    
1599
  @locking.ssynchronized(_config_lock)
1600
  def MarkInstanceDisksActive(self, inst_uuid):
1601
    """Mark the status of instance disks active.
1602

1603
    """
1604
    self._SetInstanceStatus(inst_uuid, None, True)
1605

    
1606
  @locking.ssynchronized(_config_lock)
1607
  def MarkInstanceDisksInactive(self, inst_uuid):
1608
    """Mark the status of instance disks inactive.
1609

1610
    """
1611
    self._SetInstanceStatus(inst_uuid, None, False)
1612

    
1613
  def _UnlockedGetInstanceList(self):
1614
    """Get the list of instances.
1615

1616
    This function is for internal use, when the config lock is already held.
1617

1618
    """
1619
    return self._config_data.instances.keys()
1620

    
1621
  @locking.ssynchronized(_config_lock, shared=1)
1622
  def GetInstanceList(self):
1623
    """Get the list of instances.
1624

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

1627
    """
1628
    return self._UnlockedGetInstanceList()
1629

    
1630
  def ExpandInstanceName(self, short_name):
1631
    """Attempt to expand an incomplete instance name.
1632

1633
    """
1634
    # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1635
    all_insts = self.GetAllInstancesInfo().values()
1636
    expanded_name = _MatchNameComponentIgnoreCase(
1637
                      short_name, [inst.name for inst in all_insts])
1638

    
1639
    if expanded_name is not None:
1640
      # there has to be exactly one instance with that name
1641
      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1642
      return (inst.uuid, inst.name)
1643
    else:
1644
      return (None, None)
1645

    
1646
  def _UnlockedGetInstanceInfo(self, inst_uuid):
1647
    """Returns information about an instance.
1648

1649
    This function is for internal use, when the config lock is already held.
1650

1651
    """
1652
    if inst_uuid not in self._config_data.instances:
1653
      return None
1654

    
1655
    return self._config_data.instances[inst_uuid]
1656

    
1657
  @locking.ssynchronized(_config_lock, shared=1)
1658
  def GetInstanceInfo(self, inst_uuid):
1659
    """Returns information about an instance.
1660

1661
    It takes the information from the configuration file. Other information of
1662
    an instance are taken from the live systems.
1663

1664
    @param inst_uuid: UUID of the instance
1665

1666
    @rtype: L{objects.Instance}
1667
    @return: the instance object
1668

1669
    """
1670
    return self._UnlockedGetInstanceInfo(inst_uuid)
1671

    
1672
  @locking.ssynchronized(_config_lock, shared=1)
1673
  def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1674
    """Returns set of node group UUIDs for instance's nodes.
1675

1676
    @rtype: frozenset
1677

1678
    """
1679
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1680
    if not instance:
1681
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1682

    
1683
    if primary_only:
1684
      nodes = [instance.primary_node]
1685
    else:
1686
      nodes = instance.all_nodes
1687

    
1688
    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1689
                     for node_uuid in nodes)
1690

    
1691
  @locking.ssynchronized(_config_lock, shared=1)
1692
  def GetInstanceNetworks(self, inst_uuid):
1693
    """Returns set of network UUIDs for instance's nics.
1694

1695
    @rtype: frozenset
1696

1697
    """
1698
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1699
    if not instance:
1700
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1701

    
1702
    networks = set()
1703
    for nic in instance.nics:
1704
      if nic.network:
1705
        networks.add(nic.network)
1706

    
1707
    return frozenset(networks)
1708

    
1709
  @locking.ssynchronized(_config_lock, shared=1)
1710
  def GetMultiInstanceInfo(self, inst_uuids):
1711
    """Get the configuration of multiple instances.
1712

1713
    @param inst_uuids: list of instance UUIDs
1714
    @rtype: list
1715
    @return: list of tuples (instance UUID, instance_info), where
1716
        instance_info is what would GetInstanceInfo return for the
1717
        node, while keeping the original order
1718

1719
    """
1720
    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1721

    
1722
  @locking.ssynchronized(_config_lock, shared=1)
1723
  def GetMultiInstanceInfoByName(self, inst_names):
1724
    """Get the configuration of multiple instances.
1725

1726
    @param inst_names: list of instance names
1727
    @rtype: list
1728
    @return: list of tuples (instance, instance_info), where
1729
        instance_info is what would GetInstanceInfo return for the
1730
        node, while keeping the original order
1731

1732
    """
1733
    result = []
1734
    for name in inst_names:
1735
      instance = self._UnlockedGetInstanceInfoByName(name)
1736
      result.append((instance.uuid, instance))
1737
    return result
1738

    
1739
  @locking.ssynchronized(_config_lock, shared=1)
1740
  def GetAllInstancesInfo(self):
1741
    """Get the configuration of all instances.
1742

1743
    @rtype: dict
1744
    @return: dict of (instance, instance_info), where instance_info is what
1745
              would GetInstanceInfo return for the node
1746

1747
    """
1748
    return self._UnlockedGetAllInstancesInfo()
1749

    
1750
  def _UnlockedGetAllInstancesInfo(self):
1751
    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1752
                    for inst_uuid in self._UnlockedGetInstanceList()])
1753
    return my_dict
1754

    
1755
  @locking.ssynchronized(_config_lock, shared=1)
1756
  def GetInstancesInfoByFilter(self, filter_fn):
1757
    """Get instance configuration with a filter.
1758

1759
    @type filter_fn: callable
1760
    @param filter_fn: Filter function receiving instance object as parameter,
1761
      returning boolean. Important: this function is called while the
1762
      configuration locks is held. It must not do any complex work or call
1763
      functions potentially leading to a deadlock. Ideally it doesn't call any
1764
      other functions and just compares instance attributes.
1765

1766
    """
1767
    return dict((uuid, inst)
1768
                for (uuid, inst) in self._config_data.instances.items()
1769
                if filter_fn(inst))
1770

    
1771
  @locking.ssynchronized(_config_lock, shared=1)
1772
  def GetInstanceInfoByName(self, inst_name):
1773
    """Get the L{objects.Instance} object for a named instance.
1774

1775
    @param inst_name: name of the instance to get information for
1776
    @type inst_name: string
1777
    @return: the corresponding L{objects.Instance} instance or None if no
1778
          information is available
1779

1780
    """
1781
    return self._UnlockedGetInstanceInfoByName(inst_name)
1782

    
1783
  def _UnlockedGetInstanceInfoByName(self, inst_name):
1784
    for inst in self._UnlockedGetAllInstancesInfo().values():
1785
      if inst.name == inst_name:
1786
        return inst
1787
    return None
1788

    
1789
  def _UnlockedGetInstanceName(self, inst_uuid):
1790
    inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1791
    if inst_info is None:
1792
      raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1793
    return inst_info.name
1794

    
1795
  @locking.ssynchronized(_config_lock, shared=1)
1796
  def GetInstanceName(self, inst_uuid):
1797
    """Gets the instance name for the passed instance.
1798

1799
    @param inst_uuid: instance UUID to get name for
1800
    @type inst_uuid: string
1801
    @rtype: string
1802
    @return: instance name
1803

1804
    """
1805
    return self._UnlockedGetInstanceName(inst_uuid)
1806

    
1807
  @locking.ssynchronized(_config_lock, shared=1)
1808
  def GetInstanceNames(self, inst_uuids):
1809
    """Gets the instance names for the passed list of nodes.
1810

1811
    @param inst_uuids: list of instance UUIDs to get names for
1812
    @type inst_uuids: list of strings
1813
    @rtype: list of strings
1814
    @return: list of instance names
1815

1816
    """
1817
    return self._UnlockedGetInstanceNames(inst_uuids)
1818

    
1819
  def _UnlockedGetInstanceNames(self, inst_uuids):
1820
    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1821

    
1822
  @locking.ssynchronized(_config_lock)
1823
  def AddNode(self, node, ec_id):
1824
    """Add a node to the configuration.
1825

1826
    @type node: L{objects.Node}
1827
    @param node: a Node instance
1828

1829
    """
1830
    logging.info("Adding node %s to configuration", node.name)
1831

    
1832
    self._EnsureUUID(node, ec_id)
1833

    
1834
    node.serial_no = 1
1835
    node.ctime = node.mtime = time.time()
1836
    self._UnlockedAddNodeToGroup(node.uuid, node.group)
1837
    self._config_data.nodes[node.uuid] = node
1838
    self._config_data.cluster.serial_no += 1
1839
    self._WriteConfig()
1840

    
1841
  @locking.ssynchronized(_config_lock)
1842
  def RemoveNode(self, node_uuid):
1843
    """Remove a node from the configuration.
1844

1845
    """
1846
    logging.info("Removing node %s from configuration", node_uuid)
1847

    
1848
    if node_uuid not in self._config_data.nodes:
1849
      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1850

    
1851
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1852
    del self._config_data.nodes[node_uuid]
1853
    self._config_data.cluster.serial_no += 1
1854
    self._WriteConfig()
1855

    
1856
  def ExpandNodeName(self, short_name):
1857
    """Attempt to expand an incomplete node name into a node UUID.
1858

1859
    """
1860
    # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1861
    all_nodes = self.GetAllNodesInfo().values()
1862
    expanded_name = _MatchNameComponentIgnoreCase(
1863
                      short_name, [node.name for node in all_nodes])
1864

    
1865
    if expanded_name is not None:
1866
      # there has to be exactly one node with that name
1867
      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1868
      return (node.uuid, node.name)
1869
    else:
1870
      return (None, None)
1871

    
1872
  def _UnlockedGetNodeInfo(self, node_uuid):
1873
    """Get the configuration of a node, as stored in the config.
1874

1875
    This function is for internal use, when the config lock is already
1876
    held.
1877

1878
    @param node_uuid: the node UUID
1879

1880
    @rtype: L{objects.Node}
1881
    @return: the node object
1882

1883
    """
1884
    if node_uuid not in self._config_data.nodes:
1885
      return None
1886

    
1887
    return self._config_data.nodes[node_uuid]
1888

    
1889
  @locking.ssynchronized(_config_lock, shared=1)
1890
  def GetNodeInfo(self, node_uuid):
1891
    """Get the configuration of a node, as stored in the config.
1892

1893
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1894

1895
    @param node_uuid: the node UUID
1896

1897
    @rtype: L{objects.Node}
1898
    @return: the node object
1899

1900
    """
1901
    return self._UnlockedGetNodeInfo(node_uuid)
1902

    
1903
  @locking.ssynchronized(_config_lock, shared=1)
1904
  def GetNodeInstances(self, node_uuid):
1905
    """Get the instances of a node, as stored in the config.
1906

1907
    @param node_uuid: the node UUID
1908

1909
    @rtype: (list, list)
1910
    @return: a tuple with two lists: the primary and the secondary instances
1911

1912
    """
1913
    pri = []
1914
    sec = []
1915
    for inst in self._config_data.instances.values():
1916
      if inst.primary_node == node_uuid:
1917
        pri.append(inst.uuid)
1918
      if node_uuid in inst.secondary_nodes:
1919
        sec.append(inst.uuid)
1920
    return (pri, sec)
1921

    
1922
  @locking.ssynchronized(_config_lock, shared=1)
1923
  def GetNodeGroupInstances(self, uuid, primary_only=False):
1924
    """Get the instances of a node group.
1925

1926
    @param uuid: Node group UUID
1927
    @param primary_only: Whether to only consider primary nodes
1928
    @rtype: frozenset
1929
    @return: List of instance UUIDs in node group
1930

1931
    """
1932
    if primary_only:
1933
      nodes_fn = lambda inst: [inst.primary_node]
1934
    else:
1935
      nodes_fn = lambda inst: inst.all_nodes
1936

    
1937
    return frozenset(inst.uuid
1938
                     for inst in self._config_data.instances.values()
1939
                     for node_uuid in nodes_fn(inst)
1940
                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1941

    
1942
  def _UnlockedGetHvparamsString(self, hvname):
1943
    """Return the string representation of the list of hyervisor parameters of
1944
    the given hypervisor.
1945

1946
    @see: C{GetHvparams}
1947

1948
    """
1949
    result = ""
1950
    hvparams = self._config_data.cluster.hvparams[hvname]
1951
    for key in hvparams:
1952
      result += "%s=%s\n" % (key, hvparams[key])
1953
    return result
1954

    
1955
  @locking.ssynchronized(_config_lock, shared=1)
1956
  def GetHvparamsString(self, hvname):
1957
    """Return the hypervisor parameters of the given hypervisor.
1958

1959
    @type hvname: string
1960
    @param hvname: name of a hypervisor
1961
    @rtype: string
1962
    @return: string containing key-value-pairs, one pair on each line;
1963
      format: KEY=VALUE
1964

1965
    """
1966
    return self._UnlockedGetHvparamsString(hvname)
1967

    
1968
  def _UnlockedGetNodeList(self):
1969
    """Return the list of nodes which are in the configuration.
1970

1971
    This function is for internal use, when the config lock is already
1972
    held.
1973

1974
    @rtype: list
1975

1976
    """
1977
    return self._config_data.nodes.keys()
1978

    
1979
  @locking.ssynchronized(_config_lock, shared=1)
1980
  def GetNodeList(self):
1981
    """Return the list of nodes which are in the configuration.
1982

1983
    """
1984
    return self._UnlockedGetNodeList()
1985

    
1986
  def _UnlockedGetOnlineNodeList(self):
1987
    """Return the list of nodes which are online.
1988

1989
    """
1990
    all_nodes = [self._UnlockedGetNodeInfo(node)
1991
                 for node in self._UnlockedGetNodeList()]
1992
    return [node.uuid for node in all_nodes if not node.offline]
1993

    
1994
  @locking.ssynchronized(_config_lock, shared=1)
1995
  def GetOnlineNodeList(self):
1996
    """Return the list of nodes which are online.
1997

1998
    """
1999
    return self._UnlockedGetOnlineNodeList()
2000

    
2001
  @locking.ssynchronized(_config_lock, shared=1)
2002
  def GetVmCapableNodeList(self):
2003
    """Return the list of nodes which are not vm capable.
2004

2005
    """
2006
    all_nodes = [self._UnlockedGetNodeInfo(node)
2007
                 for node in self._UnlockedGetNodeList()]
2008
    return [node.uuid for node in all_nodes if node.vm_capable]
2009

    
2010
  @locking.ssynchronized(_config_lock, shared=1)
2011
  def GetNonVmCapableNodeList(self):
2012
    """Return the list of nodes which are not vm capable.
2013

2014
    """
2015
    all_nodes = [self._UnlockedGetNodeInfo(node)
2016
                 for node in self._UnlockedGetNodeList()]
2017
    return [node.uuid for node in all_nodes if not node.vm_capable]
2018

    
2019
  @locking.ssynchronized(_config_lock, shared=1)
2020
  def GetMultiNodeInfo(self, node_uuids):
2021
    """Get the configuration of multiple nodes.
2022

2023
    @param node_uuids: list of node UUIDs
2024
    @rtype: list
2025
    @return: list of tuples of (node, node_info), where node_info is
2026
        what would GetNodeInfo return for the node, in the original
2027
        order
2028

2029
    """
2030
    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
2031

    
2032
  def _UnlockedGetAllNodesInfo(self):
2033
    """Gets configuration of all nodes.
2034

2035
    @note: See L{GetAllNodesInfo}
2036

2037
    """
2038
    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
2039
                 for node_uuid in self._UnlockedGetNodeList()])
2040

    
2041
  @locking.ssynchronized(_config_lock, shared=1)
2042
  def GetAllNodesInfo(self):
2043
    """Get the configuration of all nodes.
2044

2045
    @rtype: dict
2046
    @return: dict of (node, node_info), where node_info is what
2047
              would GetNodeInfo return for the node
2048

2049
    """
2050
    return self._UnlockedGetAllNodesInfo()
2051

    
2052
  def _UnlockedGetNodeInfoByName(self, node_name):
2053
    for node in self._UnlockedGetAllNodesInfo().values():
2054
      if node.name == node_name:
2055
        return node
2056
    return None
2057

    
2058
  @locking.ssynchronized(_config_lock, shared=1)
2059
  def GetNodeInfoByName(self, node_name):
2060
    """Get the L{objects.Node} object for a named node.
2061

2062
    @param node_name: name of the node to get information for
2063
    @type node_name: string
2064
    @return: the corresponding L{objects.Node} instance or None if no
2065
          information is available
2066

2067
    """
2068
    return self._UnlockedGetNodeInfoByName(node_name)
2069

    
2070
  @locking.ssynchronized(_config_lock, shared=1)
2071
  def GetNodeGroupInfoByName(self, nodegroup_name):
2072
    """Get the L{objects.NodeGroup} object for a named node group.
2073

2074
    @param nodegroup_name: name of the node group to get information for
2075
    @type nodegroup_name: string
2076
    @return: the corresponding L{objects.NodeGroup} instance or None if no
2077
          information is available
2078

2079
    """
2080
    for nodegroup in self._UnlockedGetAllNodeGroupsInfo().values():
2081
      if nodegroup.name == nodegroup_name:
2082
        return nodegroup
2083
    return None
2084

    
2085
  def _UnlockedGetNodeName(self, node_spec):
2086
    if isinstance(node_spec, objects.Node):
2087
      return node_spec.name
2088
    elif isinstance(node_spec, basestring):
2089
      node_info = self._UnlockedGetNodeInfo(node_spec)
2090
      if node_info is None:
2091
        raise errors.OpExecError("Unknown node: %s" % node_spec)
2092
      return node_info.name
2093
    else:
2094
      raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2095

    
2096
  @locking.ssynchronized(_config_lock, shared=1)
2097
  def GetNodeName(self, node_spec):
2098
    """Gets the node name for the passed node.
2099

2100
    @param node_spec: node to get names for
2101
    @type node_spec: either node UUID or a L{objects.Node} object
2102
    @rtype: string
2103
    @return: node name
2104

2105
    """
2106
    return self._UnlockedGetNodeName(node_spec)
2107

    
2108
  def _UnlockedGetNodeNames(self, node_specs):
2109
    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2110

    
2111
  @locking.ssynchronized(_config_lock, shared=1)
2112
  def GetNodeNames(self, node_specs):
2113
    """Gets the node names for the passed list of nodes.
2114

2115
    @param node_specs: list of nodes to get names for
2116
    @type node_specs: list of either node UUIDs or L{objects.Node} objects
2117
    @rtype: list of strings
2118
    @return: list of node names
2119

2120
    """
2121
    return self._UnlockedGetNodeNames(node_specs)
2122

    
2123
  @locking.ssynchronized(_config_lock, shared=1)
2124
  def GetNodeGroupsFromNodes(self, node_uuids):
2125
    """Returns groups for a list of nodes.
2126

2127
    @type node_uuids: list of string
2128
    @param node_uuids: List of node UUIDs
2129
    @rtype: frozenset
2130

2131
    """
2132
    return frozenset(self._UnlockedGetNodeInfo(uuid).group
2133
                     for uuid in node_uuids)
2134

    
2135
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2136
    """Get the number of current and maximum desired and possible candidates.
2137

2138
    @type exceptions: list
2139
    @param exceptions: if passed, list of nodes that should be ignored
2140
    @rtype: tuple
2141
    @return: tuple of (current, desired and possible, possible)
2142

2143
    """
2144
    mc_now = mc_should = mc_max = 0
2145
    for node in self._config_data.nodes.values():
2146
      if exceptions and node.uuid in exceptions:
2147
        continue
2148
      if not (node.offline or node.drained) and node.master_capable:
2149
        mc_max += 1
2150
      if node.master_candidate:
2151
        mc_now += 1
2152
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2153
    return (mc_now, mc_should, mc_max)
2154

    
2155
  @locking.ssynchronized(_config_lock, shared=1)
2156
  def GetMasterCandidateStats(self, exceptions=None):
2157
    """Get the number of current and maximum possible candidates.
2158

2159
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2160

2161
    @type exceptions: list
2162
    @param exceptions: if passed, list of nodes that should be ignored
2163
    @rtype: tuple
2164
    @return: tuple of (current, max)
2165

2166
    """
2167
    return self._UnlockedGetMasterCandidateStats(exceptions)
2168

    
2169
  @locking.ssynchronized(_config_lock)
2170
  def MaintainCandidatePool(self, exception_node_uuids):
2171
    """Try to grow the candidate pool to the desired size.
2172

2173
    @type exception_node_uuids: list
2174
    @param exception_node_uuids: if passed, list of nodes that should be ignored
2175
    @rtype: list
2176
    @return: list with the adjusted nodes (L{objects.Node} instances)
2177

2178
    """
2179
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2180
                          exception_node_uuids)
2181
    mod_list = []
2182
    if mc_now < mc_max:
2183
      node_list = self._config_data.nodes.keys()
2184
      random.shuffle(node_list)
2185
      for uuid in node_list:
2186
        if mc_now >= mc_max:
2187
          break
2188
        node = self._config_data.nodes[uuid]
2189
        if (node.master_candidate or node.offline or node.drained or
2190
            node.uuid in exception_node_uuids or not node.master_capable):
2191
          continue
2192
        mod_list.append(node)
2193
        node.master_candidate = True
2194
        node.serial_no += 1
2195
        mc_now += 1
2196
      if mc_now != mc_max:
2197
        # this should not happen
2198
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
2199
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
2200
      if mod_list:
2201
        self._config_data.cluster.serial_no += 1
2202
        self._WriteConfig()
2203

    
2204
    return mod_list
2205

    
2206
  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2207
    """Add a given node to the specified group.
2208

2209
    """
2210
    if nodegroup_uuid not in self._config_data.nodegroups:
2211
      # This can happen if a node group gets deleted between its lookup and
2212
      # when we're adding the first node to it, since we don't keep a lock in
2213
      # the meantime. It's ok though, as we'll fail cleanly if the node group
2214
      # is not found anymore.
2215
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2216
    if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2217
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2218

    
2219
  def _UnlockedRemoveNodeFromGroup(self, node):
2220
    """Remove a given node from its group.
2221

2222
    """
2223
    nodegroup = node.group
2224
    if nodegroup not in self._config_data.nodegroups:
2225
      logging.warning("Warning: node '%s' has unknown node group '%s'"
2226
                      " (while being removed from it)", node.uuid, nodegroup)
2227
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
2228
    if node.uuid not in nodegroup_obj.members:
2229
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
2230
                      " (while being removed from it)", node.uuid, nodegroup)
2231
    else:
2232
      nodegroup_obj.members.remove(node.uuid)
2233

    
2234
  @locking.ssynchronized(_config_lock)
2235
  def AssignGroupNodes(self, mods):
2236
    """Changes the group of a number of nodes.
2237

2238
    @type mods: list of tuples; (node name, new group UUID)
2239
    @param mods: Node membership modifications
2240

2241
    """
2242
    groups = self._config_data.nodegroups
2243
    nodes = self._config_data.nodes
2244

    
2245
    resmod = []
2246

    
2247
    # Try to resolve UUIDs first
2248
    for (node_uuid, new_group_uuid) in mods:
2249
      try:
2250
        node = nodes[node_uuid]
2251
      except KeyError:
2252
        raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2253

    
2254
      if node.group == new_group_uuid:
2255
        # Node is being assigned to its current group
2256
        logging.debug("Node '%s' was assigned to its current group (%s)",
2257
                      node_uuid, node.group)
2258
        continue
2259

    
2260
      # Try to find current group of node
2261
      try:
2262
        old_group = groups[node.group]
2263
      except KeyError:
2264
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2265
                                        node.group)
2266

    
2267
      # Try to find new group for node
2268
      try:
2269
        new_group = groups[new_group_uuid]
2270
      except KeyError:
2271
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2272
                                        new_group_uuid)
2273

    
2274
      assert node.uuid in old_group.members, \
2275
        ("Inconsistent configuration: node '%s' not listed in members for its"
2276
         " old group '%s'" % (node.uuid, old_group.uuid))
2277
      assert node.uuid not in new_group.members, \
2278
        ("Inconsistent configuration: node '%s' already listed in members for"
2279
         " its new group '%s'" % (node.uuid, new_group.uuid))
2280

    
2281
      resmod.append((node, old_group, new_group))
2282

    
2283
    # Apply changes
2284
    for (node, old_group, new_group) in resmod:
2285
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2286
        "Assigning to current group is not possible"
2287

    
2288
      node.group = new_group.uuid
2289

    
2290
      # Update members of involved groups
2291
      if node.uuid in old_group.members:
2292
        old_group.members.remove(node.uuid)
2293
      if node.uuid not in new_group.members:
2294
        new_group.members.append(node.uuid)
2295

    
2296
    # Update timestamps and serials (only once per node/group object)
2297
    now = time.time()
2298
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2299
      obj.serial_no += 1
2300
      obj.mtime = now
2301

    
2302
    # Force ssconf update
2303
    self._config_data.cluster.serial_no += 1
2304

    
2305
    self._WriteConfig()
2306

    
2307
  def _BumpSerialNo(self):
2308
    """Bump up the serial number of the config.
2309

2310
    """
2311
    self._config_data.serial_no += 1
2312
    self._config_data.mtime = time.time()
2313

    
2314
  def _AllUUIDObjects(self):
2315
    """Returns all objects with uuid attributes.
2316

2317
    """
2318
    return (self._config_data.instances.values() +
2319
            self._config_data.nodes.values() +
2320
            self._config_data.nodegroups.values() +
2321
            self._config_data.networks.values() +
2322
            self._AllDisks() +
2323
            self._AllNICs() +
2324
            [self._config_data.cluster])
2325

    
2326
  def _OpenConfig(self, accept_foreign):
2327
    """Read the config data from disk.
2328

2329
    """
2330
    raw_data = utils.ReadFile(self._cfg_file)
2331

    
2332
    try:
2333
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2334
    except Exception, err:
2335
      raise errors.ConfigurationError(err)
2336

    
2337
    # Make sure the configuration has the right version
2338
    _ValidateConfig(data)
2339

    
2340
    if (not hasattr(data, "cluster") or
2341
        not hasattr(data.cluster, "rsahostkeypub")):
2342
      raise errors.ConfigurationError("Incomplete configuration"
2343
                                      " (missing cluster.rsahostkeypub)")
2344

    
2345
    if not data.cluster.master_node in data.nodes:
2346
      msg = ("The configuration denotes node %s as master, but does not"
2347
             " contain information about this node" %
2348
             data.cluster.master_node)
2349
      raise errors.ConfigurationError(msg)
2350

    
2351
    master_info = data.nodes[data.cluster.master_node]
2352
    if master_info.name != self._my_hostname and not accept_foreign:
2353
      msg = ("The configuration denotes node %s as master, while my"
2354
             " hostname is %s; opening a foreign configuration is only"
2355
             " possible in accept_foreign mode" %
2356
             (master_info.name, self._my_hostname))
2357
      raise errors.ConfigurationError(msg)
2358

    
2359
    self._config_data = data
2360
    # reset the last serial as -1 so that the next write will cause
2361
    # ssconf update
2362
    self._last_cluster_serial = -1
2363

    
2364
    # Upgrade configuration if needed
2365
    self._UpgradeConfig()
2366

    
2367
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2368

    
2369
  def _UpgradeConfig(self):
2370
    """Run any upgrade steps.
2371

2372
    This method performs both in-object upgrades and also update some data
2373
    elements that need uniqueness across the whole configuration or interact
2374
    with other objects.
2375

2376
    @warning: this function will call L{_WriteConfig()}, but also
2377
        L{DropECReservations} so it needs to be called only from a
2378
        "safe" place (the constructor). If one wanted to call it with
2379
        the lock held, a DropECReservationUnlocked would need to be
2380
        created first, to avoid causing deadlock.
2381

2382
    """
2383
    # Keep a copy of the persistent part of _config_data to check for changes
2384
    # Serialization doesn't guarantee order in dictionaries
2385
    oldconf = copy.deepcopy(self._config_data.ToDict())
2386

    
2387
    # In-object upgrades
2388
    self._config_data.UpgradeConfig()
2389

    
2390
    for item in self._AllUUIDObjects():
2391
      if item.uuid is None:
2392
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2393
    if not self._config_data.nodegroups:
2394
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2395
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2396
                                            members=[])
2397
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2398
    for node in self._config_data.nodes.values():
2399
      if not node.group:
2400
        node.group = self.LookupNodeGroup(None)
2401
      # This is technically *not* an upgrade, but needs to be done both when
2402
      # nodegroups are being added, and upon normally loading the config,
2403
      # because the members list of a node group is discarded upon
2404
      # serializing/deserializing the object.
2405
      self._UnlockedAddNodeToGroup(node.uuid, node.group)
2406

    
2407
    modified = (oldconf != self._config_data.ToDict())
2408
    if modified:
2409
      self._WriteConfig()
2410
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2411
      # only called at config init time, without the lock held
2412
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2413
    else:
2414
      config_errors = self._UnlockedVerifyConfig()
2415
      if config_errors:
2416
        errmsg = ("Loaded configuration data is not consistent: %s" %
2417
                  (utils.CommaJoin(config_errors)))
2418
        logging.critical(errmsg)
2419

    
2420
  def _DistributeConfig(self, feedback_fn):
2421
    """Distribute the configuration to the other nodes.
2422

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

2426
    """
2427
    if self._offline:
2428
      return True
2429

    
2430
    bad = False
2431

    
2432
    node_list = []
2433
    addr_list = []
2434
    myhostname = self._my_hostname
2435
    # we can skip checking whether _UnlockedGetNodeInfo returns None
2436
    # since the node list comes from _UnlocketGetNodeList, and we are
2437
    # called with the lock held, so no modifications should take place
2438
    # in between
2439
    for node_uuid in self._UnlockedGetNodeList():
2440
      node_info = self._UnlockedGetNodeInfo(node_uuid)
2441
      if node_info.name == myhostname or not node_info.master_candidate:
2442
        continue
2443
      node_list.append(node_info.name)
2444
      addr_list.append(node_info.primary_ip)
2445

    
2446
    # TODO: Use dedicated resolver talking to config writer for name resolution
2447
    result = \
2448
      self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2449
    for to_node, to_result in result.items():
2450
      msg = to_result.fail_msg
2451
      if msg:
2452
        msg = ("Copy of file %s to node %s failed: %s" %
2453
               (self._cfg_file, to_node, msg))
2454
        logging.error(msg)
2455

    
2456
        if feedback_fn:
2457
          feedback_fn(msg)
2458

    
2459
        bad = True
2460

    
2461
    return not bad
2462

    
2463
  def _WriteConfig(self, destination=None, feedback_fn=None):
2464
    """Write the configuration data to persistent storage.
2465

2466
    """
2467
    assert feedback_fn is None or callable(feedback_fn)
2468

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

    
2481
    if destination is None:
2482
      destination = self._cfg_file
2483
    self._BumpSerialNo()
2484
    txt = serializer.Dump(self._config_data.ToDict())
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

    
2499
    self.write_count += 1
2500

    
2501
    # and redistribute the config file to master candidates
2502
    self._DistributeConfig(feedback_fn)
2503

    
2504
    # Write ssconf files on all nodes (including locally)
2505
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
2506
      if not self._offline:
2507
        result = self._GetRpc(None).call_write_ssconf_files(
2508
          self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2509
          self._UnlockedGetSsconfValues())
2510

    
2511
        for nname, nresu in result.items():
2512
          msg = nresu.fail_msg
2513
          if msg:
2514
            errmsg = ("Error while uploading ssconf files to"
2515
                      " node %s: %s" % (nname, msg))
2516
            logging.warning(errmsg)
2517

    
2518
            if feedback_fn:
2519
              feedback_fn(errmsg)
2520

    
2521
      self._last_cluster_serial = self._config_data.cluster.serial_no
2522

    
2523
  def _GetAllHvparamsStrings(self, hypervisors):
2524
    """Get the hvparams of all given hypervisors from the config.
2525

2526
    @type hypervisors: list of string
2527
    @param hypervisors: list of hypervisor names
2528
    @rtype: dict of strings
2529
    @returns: dictionary mapping the hypervisor name to a string representation
2530
      of the hypervisor's hvparams
2531

2532
    """
2533
    hvparams = {}
2534
    for hv in hypervisors:
2535
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2536
    return hvparams
2537

    
2538
  @staticmethod
2539
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2540
    """Extends the ssconf_values dictionary by hvparams.
2541

2542
    @type ssconf_values: dict of strings
2543
    @param ssconf_values: dictionary mapping ssconf_keys to strings
2544
      representing the content of ssconf files
2545
    @type all_hvparams: dict of strings
2546
    @param all_hvparams: dictionary mapping hypervisor names to a string
2547
      representation of their hvparams
2548
    @rtype: same as ssconf_values
2549
    @returns: the ssconf_values dictionary extended by hvparams
2550

2551
    """
2552
    for hv in all_hvparams:
2553
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2554
      ssconf_values[ssconf_key] = all_hvparams[hv]
2555
    return ssconf_values
2556

    
2557
  def _UnlockedGetSsconfValues(self):
2558
    """Return the values needed by ssconf.
2559

2560
    @rtype: dict
2561
    @return: a dictionary with keys the ssconf names and values their
2562
        associated value
2563

2564
    """
2565
    fn = "\n".join
2566
    instance_names = utils.NiceSort(
2567
                       [inst.name for inst in
2568
                        self._UnlockedGetAllInstancesInfo().values()])
2569
    node_infos = self._UnlockedGetAllNodesInfo().values()
2570
    node_names = [node.name for node in node_infos]
2571
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2572
                    for ninfo in node_infos]
2573
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2574
                    for ninfo in node_infos]
2575

    
2576
    instance_data = fn(instance_names)
2577
    off_data = fn(node.name for node in node_infos if node.offline)
2578
    on_data = fn(node.name for node in node_infos if not node.offline)
2579
    mc_data = fn(node.name for node in node_infos if node.master_candidate)
2580
    mc_ips_data = fn(node.primary_ip for node in node_infos
2581
                     if node.master_candidate)
2582
    node_data = fn(node_names)
2583
    node_pri_ips_data = fn(node_pri_ips)
2584
    node_snd_ips_data = fn(node_snd_ips)
2585

    
2586
    cluster = self._config_data.cluster
2587
    cluster_tags = fn(cluster.GetTags())
2588

    
2589
    hypervisor_list = fn(cluster.enabled_hypervisors)
2590
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2591

    
2592
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2593

    
2594
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2595
                  self._config_data.nodegroups.values()]
2596
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2597
    networks = ["%s %s" % (net.uuid, net.name) for net in
2598
                self._config_data.networks.values()]
2599
    networks_data = fn(utils.NiceSort(networks))
2600

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

    
2637
  @locking.ssynchronized(_config_lock, shared=1)
2638
  def GetSsconfValues(self):
2639
    """Wrapper using lock around _UnlockedGetSsconf().
2640

2641
    """
2642
    return self._UnlockedGetSsconfValues()
2643

    
2644
  @locking.ssynchronized(_config_lock, shared=1)
2645
  def GetVGName(self):
2646
    """Return the volume group name.
2647

2648
    """
2649
    return self._config_data.cluster.volume_group_name
2650

    
2651
  @locking.ssynchronized(_config_lock)
2652
  def SetVGName(self, vg_name):
2653
    """Set the volume group name.
2654

2655
    """
2656
    self._config_data.cluster.volume_group_name = vg_name
2657
    self._config_data.cluster.serial_no += 1
2658
    self._WriteConfig()
2659

    
2660
  @locking.ssynchronized(_config_lock, shared=1)
2661
  def GetDRBDHelper(self):
2662
    """Return DRBD usermode helper.
2663

2664
    """
2665
    return self._config_data.cluster.drbd_usermode_helper
2666

    
2667
  @locking.ssynchronized(_config_lock)
2668
  def SetDRBDHelper(self, drbd_helper):
2669
    """Set DRBD usermode helper.
2670

2671
    """
2672
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2673
    self._config_data.cluster.serial_no += 1
2674
    self._WriteConfig()
2675

    
2676
  @locking.ssynchronized(_config_lock, shared=1)
2677
  def GetMACPrefix(self):
2678
    """Return the mac prefix.
2679

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

    
2683
  @locking.ssynchronized(_config_lock, shared=1)
2684
  def GetClusterInfo(self):
2685
    """Returns information about the cluster
2686

2687
    @rtype: L{objects.Cluster}
2688
    @return: the cluster object
2689

2690
    """
2691
    return self._config_data.cluster
2692

    
2693
  @locking.ssynchronized(_config_lock, shared=1)
2694
  def HasAnyDiskOfType(self, dev_type):
2695
    """Check if in there is at disk of the given type in the configuration.
2696

2697
    """
2698
    return self._config_data.HasAnyDiskOfType(dev_type)
2699

    
2700
  @locking.ssynchronized(_config_lock)
2701
  def Update(self, target, feedback_fn, ec_id=None):
2702
    """Notify function to be called after updates.
2703

2704
    This function must be called when an object (as returned by
2705
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2706
    caller wants the modifications saved to the backing store. Note
2707
    that all modified objects will be saved, but the target argument
2708
    is the one the caller wants to ensure that it's saved.
2709

2710
    @param target: an instance of either L{objects.Cluster},
2711
        L{objects.Node} or L{objects.Instance} which is existing in
2712
        the cluster
2713
    @param feedback_fn: Callable feedback function
2714

2715
    """
2716
    if self._config_data is None:
2717
      raise errors.ProgrammerError("Configuration file not read,"
2718
                                   " cannot save.")
2719
    update_serial = False
2720
    if isinstance(target, objects.Cluster):
2721
      test = target == self._config_data.cluster
2722
    elif isinstance(target, objects.Node):
2723
      test = target in self._config_data.nodes.values()
2724
      update_serial = True
2725
    elif isinstance(target, objects.Instance):
2726
      test = target in self._config_data.instances.values()
2727
    elif isinstance(target, objects.NodeGroup):
2728
      test = target in self._config_data.nodegroups.values()
2729
    elif isinstance(target, objects.Network):
2730
      test = target in self._config_data.networks.values()
2731
    else:
2732
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2733
                                   " ConfigWriter.Update" % type(target))
2734
    if not test:
2735
      raise errors.ConfigurationError("Configuration updated since object"
2736
                                      " has been read or unknown object")
2737
    target.serial_no += 1
2738
    target.mtime = now = time.time()
2739

    
2740
    if update_serial:
2741
      # for node updates, we need to increase the cluster serial too
2742
      self._config_data.cluster.serial_no += 1
2743
      self._config_data.cluster.mtime = now
2744

    
2745
    if isinstance(target, objects.Instance):
2746
      self._UnlockedReleaseDRBDMinors(target.uuid)
2747

    
2748
    if ec_id is not None:
2749
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2750
      self._UnlockedCommitTemporaryIps(ec_id)
2751

    
2752
    self._WriteConfig(feedback_fn=feedback_fn)
2753

    
2754
  @locking.ssynchronized(_config_lock)
2755
  def DropECReservations(self, ec_id):
2756
    """Drop per-execution-context reservations
2757

2758
    """
2759
    for rm in self._all_rms:
2760
      rm.DropECReservations(ec_id)
2761

    
2762
  @locking.ssynchronized(_config_lock, shared=1)
2763
  def GetAllNetworksInfo(self):
2764
    """Get configuration info of all the networks.
2765

2766
    """
2767
    return dict(self._config_data.networks)
2768

    
2769
  def _UnlockedGetNetworkList(self):
2770
    """Get the list of networks.
2771

2772
    This function is for internal use, when the config lock is already held.
2773

2774
    """
2775
    return self._config_data.networks.keys()
2776

    
2777
  @locking.ssynchronized(_config_lock, shared=1)
2778
  def GetNetworkList(self):
2779
    """Get the list of networks.
2780

2781
    @return: array of networks, ex. ["main", "vlan100", "200]
2782

2783
    """
2784
    return self._UnlockedGetNetworkList()
2785

    
2786
  @locking.ssynchronized(_config_lock, shared=1)
2787
  def GetNetworkNames(self):
2788
    """Get a list of network names
2789

2790
    """
2791
    names = [net.name
2792
             for net in self._config_data.networks.values()]
2793
    return names
2794

    
2795
  def _UnlockedGetNetwork(self, uuid):
2796
    """Returns information about a network.
2797

2798
    This function is for internal use, when the config lock is already held.
2799

2800
    """
2801
    if uuid not in self._config_data.networks:
2802
      return None
2803

    
2804
    return self._config_data.networks[uuid]
2805

    
2806
  @locking.ssynchronized(_config_lock, shared=1)
2807
  def GetNetwork(self, uuid):
2808
    """Returns information about a network.
2809

2810
    It takes the information from the configuration file.
2811

2812
    @param uuid: UUID of the network
2813

2814
    @rtype: L{objects.Network}
2815
    @return: the network object
2816

2817
    """
2818
    return self._UnlockedGetNetwork(uuid)
2819

    
2820
  @locking.ssynchronized(_config_lock)
2821
  def AddNetwork(self, net, ec_id, check_uuid=True):
2822
    """Add a network to the configuration.
2823

2824
    @type net: L{objects.Network}
2825
    @param net: the Network object to add
2826
    @type ec_id: string
2827
    @param ec_id: unique id for the job to use when creating a missing UUID
2828

2829
    """
2830
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2831
    self._WriteConfig()
2832

    
2833
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2834
    """Add a network to the configuration.
2835

2836
    """
2837
    logging.info("Adding network %s to configuration", net.name)
2838

    
2839
    if check_uuid:
2840
      self._EnsureUUID(net, ec_id)
2841

    
2842
    net.serial_no = 1
2843
    net.ctime = net.mtime = time.time()
2844
    self._config_data.networks[net.uuid] = net
2845
    self._config_data.cluster.serial_no += 1
2846

    
2847
  def _UnlockedLookupNetwork(self, target):
2848
    """Lookup a network's UUID.
2849

2850
    @type target: string
2851
    @param target: network name or UUID
2852
    @rtype: string
2853
    @return: network UUID
2854
    @raises errors.OpPrereqError: when the target network cannot be found
2855

2856
    """
2857
    if target is None:
2858
      return None
2859
    if target in self._config_data.networks:
2860
      return target
2861
    for net in self._config_data.networks.values():
2862
      if net.name == target:
2863
        return net.uuid
2864
    raise errors.OpPrereqError("Network '%s' not found" % target,
2865
                               errors.ECODE_NOENT)
2866

    
2867
  @locking.ssynchronized(_config_lock, shared=1)
2868
  def LookupNetwork(self, target):
2869
    """Lookup a network's UUID.
2870

2871
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2872

2873
    @type target: string
2874
    @param target: network name or UUID
2875
    @rtype: string
2876
    @return: network UUID
2877

2878
    """
2879
    return self._UnlockedLookupNetwork(target)
2880

    
2881
  @locking.ssynchronized(_config_lock)
2882
  def RemoveNetwork(self, network_uuid):
2883
    """Remove a network from the configuration.
2884

2885
    @type network_uuid: string
2886
    @param network_uuid: the UUID of the network to remove
2887

2888
    """
2889
    logging.info("Removing network %s from configuration", network_uuid)
2890

    
2891
    if network_uuid not in self._config_data.networks:
2892
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2893

    
2894
    del self._config_data.networks[network_uuid]
2895
    self._config_data.cluster.serial_no += 1
2896
    self._WriteConfig()
2897

    
2898
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2899
    """Get the netparams (mode, link) of a network.
2900

2901
    Get a network's netparams for a given node.
2902

2903
    @type net_uuid: string
2904
    @param net_uuid: network uuid
2905
    @type node_uuid: string
2906
    @param node_uuid: node UUID
2907
    @rtype: dict or None
2908
    @return: netparams
2909

2910
    """
2911
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2912
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2913
    netparams = nodegroup_info.networks.get(net_uuid, None)
2914

    
2915
    return netparams
2916

    
2917
  @locking.ssynchronized(_config_lock, shared=1)
2918
  def GetGroupNetParams(self, net_uuid, node_uuid):
2919
    """Locking wrapper of _UnlockedGetGroupNetParams()
2920

2921
    """
2922
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2923

    
2924
  @locking.ssynchronized(_config_lock, shared=1)
2925
  def CheckIPInNodeGroup(self, ip, node_uuid):
2926
    """Check IP uniqueness in nodegroup.
2927

2928
    Check networks that are connected in the node's node group
2929
    if ip is contained in any of them. Used when creating/adding
2930
    a NIC to ensure uniqueness among nodegroups.
2931

2932
    @type ip: string
2933
    @param ip: ip address
2934
    @type node_uuid: string
2935
    @param node_uuid: node UUID
2936
    @rtype: (string, dict) or (None, None)
2937
    @return: (network name, netparams)
2938

2939
    """
2940
    if ip is None:
2941
      return (None, None)
2942
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2943
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2944
    for net_uuid in nodegroup_info.networks.keys():
2945
      net_info = self._UnlockedGetNetwork(net_uuid)
2946
      pool = network.AddressPool(net_info)
2947
      if pool.Contains(ip):
2948
        return (net_info.name, nodegroup_info.networks[net_uuid])
2949

    
2950
    return (None, None)