Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 031d2db1

History | View | Annotate | Download (95.1 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 GetInstanceDiskParams(self, instance):
241
    """Get the disk params populated with inherit chain.
242

243
    @type instance: L{objects.Instance}
244
    @param instance: The instance we want to know the params for
245
    @return: A dict with the filled in disk params
246

247
    """
248
    node = self._UnlockedGetNodeInfo(instance.primary_node)
249
    nodegroup = self._UnlockedGetNodeGroup(node.group)
250
    return self._UnlockedGetGroupDiskParams(nodegroup)
251

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

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

260
    """
261
    return self._UnlockedGetGroupDiskParams(group)
262

    
263
  def _UnlockedGetGroupDiskParams(self, group):
264
    """Get the disk params populated with inherit chain down to node-group.
265

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

270
    """
271
    return self._config_data.cluster.SimpleFillDP(group.diskparams)
272

    
273
  def _UnlockedGetNetworkMACPrefix(self, net_uuid):
274
    """Return the network mac prefix if it exists or the cluster level default.
275

276
    """
277
    prefix = None
278
    if net_uuid:
279
      nobj = self._UnlockedGetNetwork(net_uuid)
280
      if nobj.mac_prefix:
281
        prefix = nobj.mac_prefix
282

    
283
    return prefix
284

    
285
  def _GenerateOneMAC(self, prefix=None):
286
    """Return a function that randomly generates a MAC suffic
287
       and appends it to the given prefix. If prefix is not given get
288
       the cluster level default.
289

290
    """
291
    if not prefix:
292
      prefix = self._config_data.cluster.mac_prefix
293

    
294
    def GenMac():
295
      byte1 = random.randrange(0, 256)
296
      byte2 = random.randrange(0, 256)
297
      byte3 = random.randrange(0, 256)
298
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
299
      return mac
300

    
301
    return GenMac
302

    
303
  @locking.ssynchronized(_config_lock, shared=1)
304
  def GenerateMAC(self, net_uuid, ec_id):
305
    """Generate a MAC for an instance.
306

307
    This should check the current instances for duplicates.
308

309
    """
310
    existing = self._AllMACs()
311
    prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
312
    gen_mac = self._GenerateOneMAC(prefix)
313
    return self._temporary_ids.Generate(existing, gen_mac, ec_id)
314

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

319
    This only checks instances managed by this cluster, it does not
320
    check for potential collisions elsewhere.
321

322
    """
323
    all_macs = self._AllMACs()
324
    if mac in all_macs:
325
      raise errors.ReservationError("mac already in use")
326
    else:
327
      self._temporary_macs.Reserve(ec_id, mac)
328

    
329
  def _UnlockedCommitTemporaryIps(self, ec_id):
330
    """Commit all reserved IP address to their respective pools
331

332
    """
333
    for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
334
      self._UnlockedCommitIp(action, net_uuid, address)
335

    
336
  def _UnlockedCommitIp(self, action, net_uuid, address):
337
    """Commit a reserved IP address to an IP pool.
338

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

341
    """
342
    nobj = self._UnlockedGetNetwork(net_uuid)
343
    pool = network.AddressPool(nobj)
344
    if action == constants.RESERVE_ACTION:
345
      pool.Reserve(address)
346
    elif action == constants.RELEASE_ACTION:
347
      pool.Release(address)
348

    
349
  def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
350
    """Give a specific IP address back to an IP pool.
351

352
    The IP address is returned to the IP pool designated by pool_id and marked
353
    as reserved.
354

355
    """
356
    self._temporary_ips.Reserve(ec_id,
357
                                (constants.RELEASE_ACTION, address, net_uuid))
358

    
359
  @locking.ssynchronized(_config_lock, shared=1)
360
  def ReleaseIp(self, net_uuid, address, ec_id):
361
    """Give a specified IP address back to an IP pool.
362

363
    This is just a wrapper around _UnlockedReleaseIp.
364

365
    """
366
    if net_uuid:
367
      self._UnlockedReleaseIp(net_uuid, address, ec_id)
368

    
369
  @locking.ssynchronized(_config_lock, shared=1)
370
  def GenerateIp(self, net_uuid, ec_id):
371
    """Find a free IPv4 address for an instance.
372

373
    """
374
    nobj = self._UnlockedGetNetwork(net_uuid)
375
    pool = network.AddressPool(nobj)
376

    
377
    def gen_one():
378
      try:
379
        ip = pool.GenerateFree()
380
      except errors.AddressPoolError:
381
        raise errors.ReservationError("Cannot generate IP. Network is full")
382
      return (constants.RESERVE_ACTION, ip, net_uuid)
383

    
384
    _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
385
    return address
386

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

390
    """
391
    nobj = self._UnlockedGetNetwork(net_uuid)
392
    pool = network.AddressPool(nobj)
393
    try:
394
      isreserved = pool.IsReserved(address)
395
      isextreserved = pool.IsReserved(address, external=True)
396
    except errors.AddressPoolError:
397
      raise errors.ReservationError("IP address not in network")
398
    if isreserved:
399
      raise errors.ReservationError("IP address already in use")
400
    if check and isextreserved:
401
      raise errors.ReservationError("IP is externally reserved")
402

    
403
    return self._temporary_ips.Reserve(ec_id,
404
                                       (constants.RESERVE_ACTION,
405
                                        address, net_uuid))
406

    
407
  @locking.ssynchronized(_config_lock, shared=1)
408
  def ReserveIp(self, net_uuid, address, ec_id, check=True):
409
    """Reserve a given IPv4 address for use by an instance.
410

411
    """
412
    if net_uuid:
413
      return self._UnlockedReserveIp(net_uuid, address, ec_id, check)
414

    
415
  @locking.ssynchronized(_config_lock, shared=1)
416
  def ReserveLV(self, lv_name, ec_id):
417
    """Reserve an VG/LV pair for an instance.
418

419
    @type lv_name: string
420
    @param lv_name: the logical volume name to reserve
421

422
    """
423
    all_lvs = self._AllLVs()
424
    if lv_name in all_lvs:
425
      raise errors.ReservationError("LV already in use")
426
    else:
427
      self._temporary_lvs.Reserve(ec_id, lv_name)
428

    
429
  @locking.ssynchronized(_config_lock, shared=1)
430
  def GenerateDRBDSecret(self, ec_id):
431
    """Generate a DRBD secret.
432

433
    This checks the current disks for duplicates.
434

435
    """
436
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
437
                                            utils.GenerateSecret,
438
                                            ec_id)
439

    
440
  def _AllLVs(self):
441
    """Compute the list of all LVs.
442

443
    """
444
    lvnames = set()
445
    for instance in self._config_data.instances.values():
446
      node_data = instance.MapLVsByNode()
447
      for lv_list in node_data.values():
448
        lvnames.update(lv_list)
449
    return lvnames
450

    
451
  def _AllDisks(self):
452
    """Compute the list of all Disks (recursively, including children).
453

454
    """
455
    def DiskAndAllChildren(disk):
456
      """Returns a list containing the given disk and all of his children.
457

458
      """
459
      disks = [disk]
460
      if disk.children:
461
        for child_disk in disk.children:
462
          disks.extend(DiskAndAllChildren(child_disk))
463
      return disks
464

    
465
    disks = []
466
    for instance in self._config_data.instances.values():
467
      for disk in instance.disks:
468
        disks.extend(DiskAndAllChildren(disk))
469
    return disks
470

    
471
  def _AllNICs(self):
472
    """Compute the list of all NICs.
473

474
    """
475
    nics = []
476
    for instance in self._config_data.instances.values():
477
      nics.extend(instance.nics)
478
    return nics
479

    
480
  def _AllIDs(self, include_temporary):
481
    """Compute the list of all UUIDs and names we have.
482

483
    @type include_temporary: boolean
484
    @param include_temporary: whether to include the _temporary_ids set
485
    @rtype: set
486
    @return: a set of IDs
487

488
    """
489
    existing = set()
490
    if include_temporary:
491
      existing.update(self._temporary_ids.GetReserved())
492
    existing.update(self._AllLVs())
493
    existing.update(self._config_data.instances.keys())
494
    existing.update(self._config_data.nodes.keys())
495
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
496
    return existing
497

    
498
  def _GenerateUniqueID(self, ec_id):
499
    """Generate an unique UUID.
500

501
    This checks the current node, instances and disk names for
502
    duplicates.
503

504
    @rtype: string
505
    @return: the unique id
506

507
    """
508
    existing = self._AllIDs(include_temporary=False)
509
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
510

    
511
  @locking.ssynchronized(_config_lock, shared=1)
512
  def GenerateUniqueID(self, ec_id):
513
    """Generate an unique ID.
514

515
    This is just a wrapper over the unlocked version.
516

517
    @type ec_id: string
518
    @param ec_id: unique id for the job to reserve the id to
519

520
    """
521
    return self._GenerateUniqueID(ec_id)
522

    
523
  def _AllMACs(self):
524
    """Return all MACs present in the config.
525

526
    @rtype: list
527
    @return: the list of all MACs
528

529
    """
530
    result = []
531
    for instance in self._config_data.instances.values():
532
      for nic in instance.nics:
533
        result.append(nic.mac)
534

    
535
    return result
536

    
537
  def _AllDRBDSecrets(self):
538
    """Return all DRBD secrets present in the config.
539

540
    @rtype: list
541
    @return: the list of all DRBD secrets
542

543
    """
544
    def helper(disk, result):
545
      """Recursively gather secrets from this disk."""
546
      if disk.dev_type == constants.DT_DRBD8:
547
        result.append(disk.logical_id[5])
548
      if disk.children:
549
        for child in disk.children:
550
          helper(child, result)
551

    
552
    result = []
553
    for instance in self._config_data.instances.values():
554
      for disk in instance.disks:
555
        helper(disk, result)
556

    
557
    return result
558

    
559
  def _CheckDiskIDs(self, disk, l_ids):
560
    """Compute duplicate disk IDs
561

562
    @type disk: L{objects.Disk}
563
    @param disk: the disk at which to start searching
564
    @type l_ids: list
565
    @param l_ids: list of current logical ids
566
    @rtype: list
567
    @return: a list of error messages
568

569
    """
570
    result = []
571
    if disk.logical_id is not None:
572
      if disk.logical_id in l_ids:
573
        result.append("duplicate logical id %s" % str(disk.logical_id))
574
      else:
575
        l_ids.append(disk.logical_id)
576

    
577
    if disk.children:
578
      for child in disk.children:
579
        result.extend(self._CheckDiskIDs(child, l_ids))
580
    return result
581

    
582
  def _UnlockedVerifyConfig(self):
583
    """Verify function.
584

585
    @rtype: list
586
    @return: a list of error messages; a non-empty list signifies
587
        configuration errors
588

589
    """
590
    # pylint: disable=R0914
591
    result = []
592
    seen_macs = []
593
    ports = {}
594
    data = self._config_data
595
    cluster = data.cluster
596
    seen_lids = []
597

    
598
    # global cluster checks
599
    if not cluster.enabled_hypervisors:
600
      result.append("enabled hypervisors list doesn't have any entries")
601
    invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
602
    if invalid_hvs:
603
      result.append("enabled hypervisors contains invalid entries: %s" %
604
                    utils.CommaJoin(invalid_hvs))
605
    missing_hvp = (set(cluster.enabled_hypervisors) -
606
                   set(cluster.hvparams.keys()))
607
    if missing_hvp:
608
      result.append("hypervisor parameters missing for the enabled"
609
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
610

    
611
    if not cluster.enabled_disk_templates:
612
      result.append("enabled disk templates list doesn't have any entries")
613
    invalid_disk_templates = set(cluster.enabled_disk_templates) \
614
                               - constants.DISK_TEMPLATES
615
    if invalid_disk_templates:
616
      result.append("enabled disk templates list contains invalid entries:"
617
                    " %s" % utils.CommaJoin(invalid_disk_templates))
618

    
619
    if cluster.master_node not in data.nodes:
620
      result.append("cluster has invalid primary node '%s'" %
621
                    cluster.master_node)
622

    
623
    def _helper(owner, attr, value, template):
624
      try:
625
        utils.ForceDictType(value, template)
626
      except errors.GenericError, err:
627
        result.append("%s has invalid %s: %s" % (owner, attr, err))
628

    
629
    def _helper_nic(owner, params):
630
      try:
631
        objects.NIC.CheckParameterSyntax(params)
632
      except errors.ConfigurationError, err:
633
        result.append("%s has invalid nicparams: %s" % (owner, err))
634

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

    
658
    def _helper_ispecs(owner, parentkey, params):
659
      for (key, value) in params.items():
660
        fullkey = "/".join([parentkey, key])
661
        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
662

    
663
    # check cluster parameters
664
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
665
            constants.BES_PARAMETER_TYPES)
666
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
667
            constants.NICS_PARAMETER_TYPES)
668
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
669
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
670
            constants.NDS_PARAMETER_TYPES)
671
    _helper_ipolicy("cluster", cluster.ipolicy, True)
672

    
673
    if constants.DT_RBD in cluster.diskparams:
674
      access = cluster.diskparams[constants.DT_RBD][constants.RBD_ACCESS]
675
      if access not in constants.DISK_VALID_ACCESS_MODES:
676
        result.append(
677
          "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
678
            constants.DT_RBD, constants.RBD_ACCESS, access,
679
            utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
680
          )
681
        )
682

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

    
709
      # disk template checks
710
      if not instance.disk_template in data.cluster.enabled_disk_templates:
711
        result.append("instance '%s' uses the disabled disk template '%s'." %
712
                      (instance.name, instance.disk_template))
713

    
714
      # parameter checks
715
      if instance.beparams:
716
        _helper("instance %s" % instance.name, "beparams",
717
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
718

    
719
      # gather the drbd ports for duplicate checks
720
      for (idx, dsk) in enumerate(instance.disks):
721
        if dsk.dev_type in constants.DTS_DRBD:
722
          tcp_port = dsk.logical_id[2]
723
          if tcp_port not in ports:
724
            ports[tcp_port] = []
725
          ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
726
      # gather network port reservation
727
      net_port = getattr(instance, "network_port", None)
728
      if net_port is not None:
729
        if net_port not in ports:
730
          ports[net_port] = []
731
        ports[net_port].append((instance.name, "network port"))
732

    
733
      # instance disk verify
734
      for idx, disk in enumerate(instance.disks):
735
        result.extend(["instance '%s' disk %d error: %s" %
736
                       (instance.name, idx, msg) for msg in disk.Verify()])
737
        result.extend(self._CheckDiskIDs(disk, seen_lids))
738

    
739
      wrong_names = _CheckInstanceDiskIvNames(instance.disks)
740
      if wrong_names:
741
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
742
                         (idx, exp_name, actual_name))
743
                        for (idx, exp_name, actual_name) in wrong_names)
744

    
745
        result.append("Instance '%s' has wrongly named disks: %s" %
746
                      (instance.name, tmp))
747

    
748
    # cluster-wide pool of free ports
749
    for free_port in cluster.tcpudp_port_pool:
750
      if free_port not in ports:
751
        ports[free_port] = []
752
      ports[free_port].append(("cluster", "port marked as free"))
753

    
754
    # compute tcp/udp duplicate ports
755
    keys = ports.keys()
756
    keys.sort()
757
    for pnum in keys:
758
      pdata = ports[pnum]
759
      if len(pdata) > 1:
760
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
761
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
762

    
763
    # highest used tcp port check
764
    if keys:
765
      if keys[-1] > cluster.highest_used_port:
766
        result.append("Highest used port mismatch, saved %s, computed %s" %
767
                      (cluster.highest_used_port, keys[-1]))
768

    
769
    if not data.nodes[cluster.master_node].master_candidate:
770
      result.append("Master node is not a master candidate")
771

    
772
    # master candidate checks
773
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
774
    if mc_now < mc_max:
775
      result.append("Not enough master candidates: actual %d, target %d" %
776
                    (mc_now, mc_max))
777

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

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

    
822
    # drbd minors check
823
    _, duplicates = self._UnlockedComputeDRBDMap()
824
    for node, minor, instance_a, instance_b in duplicates:
825
      result.append("DRBD minor %d on node %s is assigned twice to instances"
826
                    " %s and %s" % (minor, node, instance_a, instance_b))
827

    
828
    # IP checks
829
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
830
    ips = {}
831

    
832
    def _AddIpAddress(ip, name):
833
      ips.setdefault(ip, []).append(name)
834

    
835
    _AddIpAddress(cluster.master_ip, "cluster_ip")
836

    
837
    for node in data.nodes.values():
838
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
839
      if node.secondary_ip != node.primary_ip:
840
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
841

    
842
    for instance in data.instances.values():
843
      for idx, nic in enumerate(instance.nics):
844
        if nic.ip is None:
845
          continue
846

    
847
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
848
        nic_mode = nicparams[constants.NIC_MODE]
849
        nic_link = nicparams[constants.NIC_LINK]
850

    
851
        if nic_mode == constants.NIC_MODE_BRIDGED:
852
          link = "bridge:%s" % nic_link
853
        elif nic_mode == constants.NIC_MODE_ROUTED:
854
          link = "route:%s" % nic_link
855
        else:
856
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
857

    
858
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
859
                      "instance:%s/nic:%d" % (instance.name, idx))
860

    
861
    for ip, owners in ips.items():
862
      if len(owners) > 1:
863
        result.append("IP address %s is used by multiple owners: %s" %
864
                      (ip, utils.CommaJoin(owners)))
865

    
866
    return result
867

    
868
  @locking.ssynchronized(_config_lock, shared=1)
869
  def VerifyConfig(self):
870
    """Verify function.
871

872
    This is just a wrapper over L{_UnlockedVerifyConfig}.
873

874
    @rtype: list
875
    @return: a list of error messages; a non-empty list signifies
876
        configuration errors
877

878
    """
879
    return self._UnlockedVerifyConfig()
880

    
881
  @locking.ssynchronized(_config_lock)
882
  def AddTcpUdpPort(self, port):
883
    """Adds a new port to the available port pool.
884

885
    @warning: this method does not "flush" the configuration (via
886
        L{_WriteConfig}); callers should do that themselves once the
887
        configuration is stable
888

889
    """
890
    if not isinstance(port, int):
891
      raise errors.ProgrammerError("Invalid type passed for port")
892

    
893
    self._config_data.cluster.tcpudp_port_pool.add(port)
894

    
895
  @locking.ssynchronized(_config_lock, shared=1)
896
  def GetPortList(self):
897
    """Returns a copy of the current port list.
898

899
    """
900
    return self._config_data.cluster.tcpudp_port_pool.copy()
901

    
902
  @locking.ssynchronized(_config_lock)
903
  def AllocatePort(self):
904
    """Allocate a port.
905

906
    The port will be taken from the available port pool or from the
907
    default port range (and in this case we increase
908
    highest_used_port).
909

910
    """
911
    # If there are TCP/IP ports configured, we use them first.
912
    if self._config_data.cluster.tcpudp_port_pool:
913
      port = self._config_data.cluster.tcpudp_port_pool.pop()
914
    else:
915
      port = self._config_data.cluster.highest_used_port + 1
916
      if port >= constants.LAST_DRBD_PORT:
917
        raise errors.ConfigurationError("The highest used port is greater"
918
                                        " than %s. Aborting." %
919
                                        constants.LAST_DRBD_PORT)
920
      self._config_data.cluster.highest_used_port = port
921

    
922
    self._WriteConfig()
923
    return port
924

    
925
  def _UnlockedComputeDRBDMap(self):
926
    """Compute the used DRBD minor/nodes.
927

928
    @rtype: (dict, list)
929
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
930
        the returned dict will have all the nodes in it (even if with
931
        an empty list), and a list of duplicates; if the duplicates
932
        list is not empty, the configuration is corrupted and its caller
933
        should raise an exception
934

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

    
955
    duplicates = []
956
    my_dict = dict((node_uuid, {}) for node_uuid in self._config_data.nodes)
957
    for instance in self._config_data.instances.itervalues():
958
      for disk in instance.disks:
959
        duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
960
                                            instance, disk, my_dict))
961
    for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
962
      if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
963
        duplicates.append((node_uuid, minor, inst_uuid,
964
                           my_dict[node_uuid][minor]))
965
      else:
966
        my_dict[node_uuid][minor] = inst_uuid
967
    return my_dict, duplicates
968

    
969
  @locking.ssynchronized(_config_lock)
970
  def ComputeDRBDMap(self):
971
    """Compute the used DRBD minor/nodes.
972

973
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
974

975
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
976
        the returned dict will have all the nodes in it (even if with
977
        an empty list).
978

979
    """
980
    d_map, duplicates = self._UnlockedComputeDRBDMap()
981
    if duplicates:
982
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
983
                                      str(duplicates))
984
    return d_map
985

    
986
  @locking.ssynchronized(_config_lock)
987
  def AllocateDRBDMinor(self, node_uuids, inst_uuid):
988
    """Allocate a drbd minor.
989

990
    The free minor will be automatically computed from the existing
991
    devices. A node can be given multiple times in order to allocate
992
    multiple minors. The result is the list of minors, in the same
993
    order as the passed nodes.
994

995
    @type inst_uuid: string
996
    @param inst_uuid: the instance for which we allocate minors
997

998
    """
999
    assert isinstance(inst_uuid, basestring), \
1000
           "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
1001

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

    
1042
  def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1043
    """Release temporary drbd minors allocated for a given instance.
1044

1045
    @type inst_uuid: string
1046
    @param inst_uuid: the instance for which temporary minors should be
1047
                      released
1048

1049
    """
1050
    assert isinstance(inst_uuid, basestring), \
1051
           "Invalid argument passed to ReleaseDRBDMinors"
1052
    for key, uuid in self._temporary_drbds.items():
1053
      if uuid == inst_uuid:
1054
        del self._temporary_drbds[key]
1055

    
1056
  @locking.ssynchronized(_config_lock)
1057
  def ReleaseDRBDMinors(self, inst_uuid):
1058
    """Release temporary drbd minors allocated for a given instance.
1059

1060
    This should be called on the error paths, on the success paths
1061
    it's automatically called by the ConfigWriter add and update
1062
    functions.
1063

1064
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1065

1066
    @type inst_uuid: string
1067
    @param inst_uuid: the instance for which temporary minors should be
1068
                      released
1069

1070
    """
1071
    self._UnlockedReleaseDRBDMinors(inst_uuid)
1072

    
1073
  @locking.ssynchronized(_config_lock, shared=1)
1074
  def GetConfigVersion(self):
1075
    """Get the configuration version.
1076

1077
    @return: Config version
1078

1079
    """
1080
    return self._config_data.version
1081

    
1082
  @locking.ssynchronized(_config_lock, shared=1)
1083
  def GetClusterName(self):
1084
    """Get cluster name.
1085

1086
    @return: Cluster name
1087

1088
    """
1089
    return self._config_data.cluster.cluster_name
1090

    
1091
  @locking.ssynchronized(_config_lock, shared=1)
1092
  def GetMasterNode(self):
1093
    """Get the UUID of the master node for this cluster.
1094

1095
    @return: Master node UUID
1096

1097
    """
1098
    return self._config_data.cluster.master_node
1099

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

1104
    @return: Master node hostname
1105

1106
    """
1107
    return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
1108

    
1109
  @locking.ssynchronized(_config_lock, shared=1)
1110
  def GetMasterNodeInfo(self):
1111
    """Get the master node information for this cluster.
1112

1113
    @rtype: objects.Node
1114
    @return: Master node L{objects.Node} object
1115

1116
    """
1117
    return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
1118

    
1119
  @locking.ssynchronized(_config_lock, shared=1)
1120
  def GetMasterIP(self):
1121
    """Get the IP of the master node for this cluster.
1122

1123
    @return: Master IP
1124

1125
    """
1126
    return self._config_data.cluster.master_ip
1127

    
1128
  @locking.ssynchronized(_config_lock, shared=1)
1129
  def GetMasterNetdev(self):
1130
    """Get the master network device for this cluster.
1131

1132
    """
1133
    return self._config_data.cluster.master_netdev
1134

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

1139
    """
1140
    return self._config_data.cluster.master_netmask
1141

    
1142
  @locking.ssynchronized(_config_lock, shared=1)
1143
  def GetUseExternalMipScript(self):
1144
    """Get flag representing whether to use the external master IP setup script.
1145

1146
    """
1147
    return self._config_data.cluster.use_external_mip_script
1148

    
1149
  @locking.ssynchronized(_config_lock, shared=1)
1150
  def GetFileStorageDir(self):
1151
    """Get the file storage dir for this cluster.
1152

1153
    """
1154
    return self._config_data.cluster.file_storage_dir
1155

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

1160
    """
1161
    return self._config_data.cluster.shared_file_storage_dir
1162

    
1163
  @locking.ssynchronized(_config_lock, shared=1)
1164
  def GetHypervisorType(self):
1165
    """Get the hypervisor type for this cluster.
1166

1167
    """
1168
    return self._config_data.cluster.enabled_hypervisors[0]
1169

    
1170
  @locking.ssynchronized(_config_lock, shared=1)
1171
  def GetRsaHostKey(self):
1172
    """Return the rsa hostkey from the config.
1173

1174
    @rtype: string
1175
    @return: the rsa hostkey
1176

1177
    """
1178
    return self._config_data.cluster.rsahostkeypub
1179

    
1180
  @locking.ssynchronized(_config_lock, shared=1)
1181
  def GetDsaHostKey(self):
1182
    """Return the dsa hostkey from the config.
1183

1184
    @rtype: string
1185
    @return: the dsa hostkey
1186

1187
    """
1188
    return self._config_data.cluster.dsahostkeypub
1189

    
1190
  @locking.ssynchronized(_config_lock, shared=1)
1191
  def GetDefaultIAllocator(self):
1192
    """Get the default instance allocator for this cluster.
1193

1194
    """
1195
    return self._config_data.cluster.default_iallocator
1196

    
1197
  @locking.ssynchronized(_config_lock, shared=1)
1198
  def GetPrimaryIPFamily(self):
1199
    """Get cluster primary ip family.
1200

1201
    @return: primary ip family
1202

1203
    """
1204
    return self._config_data.cluster.primary_ip_family
1205

    
1206
  @locking.ssynchronized(_config_lock, shared=1)
1207
  def GetMasterNetworkParameters(self):
1208
    """Get network parameters of the master node.
1209

1210
    @rtype: L{object.MasterNetworkParameters}
1211
    @return: network parameters of the master node
1212

1213
    """
1214
    cluster = self._config_data.cluster
1215
    result = objects.MasterNetworkParameters(
1216
      uuid=cluster.master_node, ip=cluster.master_ip,
1217
      netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1218
      ip_family=cluster.primary_ip_family)
1219

    
1220
    return result
1221

    
1222
  @locking.ssynchronized(_config_lock)
1223
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1224
    """Add a node group to the configuration.
1225

1226
    This method calls group.UpgradeConfig() to fill any missing attributes
1227
    according to their default values.
1228

1229
    @type group: L{objects.NodeGroup}
1230
    @param group: the NodeGroup object to add
1231
    @type ec_id: string
1232
    @param ec_id: unique id for the job to use when creating a missing UUID
1233
    @type check_uuid: bool
1234
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1235
                       it does, ensure that it does not exist in the
1236
                       configuration already
1237

1238
    """
1239
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1240
    self._WriteConfig()
1241

    
1242
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1243
    """Add a node group to the configuration.
1244

1245
    """
1246
    logging.info("Adding node group %s to configuration", group.name)
1247

    
1248
    # Some code might need to add a node group with a pre-populated UUID
1249
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1250
    # the "does this UUID" exist already check.
1251
    if check_uuid:
1252
      self._EnsureUUID(group, ec_id)
1253

    
1254
    try:
1255
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1256
    except errors.OpPrereqError:
1257
      pass
1258
    else:
1259
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1260
                                 " node group (UUID: %s)" %
1261
                                 (group.name, existing_uuid),
1262
                                 errors.ECODE_EXISTS)
1263

    
1264
    group.serial_no = 1
1265
    group.ctime = group.mtime = time.time()
1266
    group.UpgradeConfig()
1267

    
1268
    self._config_data.nodegroups[group.uuid] = group
1269
    self._config_data.cluster.serial_no += 1
1270

    
1271
  @locking.ssynchronized(_config_lock)
1272
  def RemoveNodeGroup(self, group_uuid):
1273
    """Remove a node group from the configuration.
1274

1275
    @type group_uuid: string
1276
    @param group_uuid: the UUID of the node group to remove
1277

1278
    """
1279
    logging.info("Removing node group %s from configuration", group_uuid)
1280

    
1281
    if group_uuid not in self._config_data.nodegroups:
1282
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1283

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

    
1287
    del self._config_data.nodegroups[group_uuid]
1288
    self._config_data.cluster.serial_no += 1
1289
    self._WriteConfig()
1290

    
1291
  def _UnlockedLookupNodeGroup(self, target):
1292
    """Lookup a node group's UUID.
1293

1294
    @type target: string or None
1295
    @param target: group name or UUID or None to look for the default
1296
    @rtype: string
1297
    @return: nodegroup UUID
1298
    @raises errors.OpPrereqError: when the target group cannot be found
1299

1300
    """
1301
    if target is None:
1302
      if len(self._config_data.nodegroups) != 1:
1303
        raise errors.OpPrereqError("More than one node group exists. Target"
1304
                                   " group must be specified explicitly.")
1305
      else:
1306
        return self._config_data.nodegroups.keys()[0]
1307
    if target in self._config_data.nodegroups:
1308
      return target
1309
    for nodegroup in self._config_data.nodegroups.values():
1310
      if nodegroup.name == target:
1311
        return nodegroup.uuid
1312
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1313
                               errors.ECODE_NOENT)
1314

    
1315
  @locking.ssynchronized(_config_lock, shared=1)
1316
  def LookupNodeGroup(self, target):
1317
    """Lookup a node group's UUID.
1318

1319
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1320

1321
    @type target: string or None
1322
    @param target: group name or UUID or None to look for the default
1323
    @rtype: string
1324
    @return: nodegroup UUID
1325

1326
    """
1327
    return self._UnlockedLookupNodeGroup(target)
1328

    
1329
  def _UnlockedGetNodeGroup(self, uuid):
1330
    """Lookup a node group.
1331

1332
    @type uuid: string
1333
    @param uuid: group UUID
1334
    @rtype: L{objects.NodeGroup} or None
1335
    @return: nodegroup object, or None if not found
1336

1337
    """
1338
    if uuid not in self._config_data.nodegroups:
1339
      return None
1340

    
1341
    return self._config_data.nodegroups[uuid]
1342

    
1343
  @locking.ssynchronized(_config_lock, shared=1)
1344
  def GetNodeGroup(self, uuid):
1345
    """Lookup a node group.
1346

1347
    @type uuid: string
1348
    @param uuid: group UUID
1349
    @rtype: L{objects.NodeGroup} or None
1350
    @return: nodegroup object, or None if not found
1351

1352
    """
1353
    return self._UnlockedGetNodeGroup(uuid)
1354

    
1355
  @locking.ssynchronized(_config_lock, shared=1)
1356
  def GetAllNodeGroupsInfo(self):
1357
    """Get the configuration of all node groups.
1358

1359
    """
1360
    return dict(self._config_data.nodegroups)
1361

    
1362
  @locking.ssynchronized(_config_lock, shared=1)
1363
  def GetNodeGroupList(self):
1364
    """Get a list of node groups.
1365

1366
    """
1367
    return self._config_data.nodegroups.keys()
1368

    
1369
  @locking.ssynchronized(_config_lock, shared=1)
1370
  def GetNodeGroupMembersByNodes(self, nodes):
1371
    """Get nodes which are member in the same nodegroups as the given nodes.
1372

1373
    """
1374
    ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1375
    return frozenset(member_uuid
1376
                     for node_uuid in nodes
1377
                     for member_uuid in
1378
                       self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1379

    
1380
  @locking.ssynchronized(_config_lock, shared=1)
1381
  def GetMultiNodeGroupInfo(self, group_uuids):
1382
    """Get the configuration of multiple node groups.
1383

1384
    @param group_uuids: List of node group UUIDs
1385
    @rtype: list
1386
    @return: List of tuples of (group_uuid, group_info)
1387

1388
    """
1389
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1390

    
1391
  @locking.ssynchronized(_config_lock)
1392
  def AddInstance(self, instance, ec_id):
1393
    """Add an instance to the config.
1394

1395
    This should be used after creating a new instance.
1396

1397
    @type instance: L{objects.Instance}
1398
    @param instance: the instance object
1399

1400
    """
1401
    if not isinstance(instance, objects.Instance):
1402
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1403

    
1404
    if instance.disk_template != constants.DT_DISKLESS:
1405
      all_lvs = instance.MapLVsByNode()
1406
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1407

    
1408
    all_macs = self._AllMACs()
1409
    for nic in instance.nics:
1410
      if nic.mac in all_macs:
1411
        raise errors.ConfigurationError("Cannot add instance %s:"
1412
                                        " MAC address '%s' already in use." %
1413
                                        (instance.name, nic.mac))
1414

    
1415
    self._CheckUniqueUUID(instance, include_temporary=False)
1416

    
1417
    instance.serial_no = 1
1418
    instance.ctime = instance.mtime = time.time()
1419
    self._config_data.instances[instance.uuid] = instance
1420
    self._config_data.cluster.serial_no += 1
1421
    self._UnlockedReleaseDRBDMinors(instance.uuid)
1422
    self._UnlockedCommitTemporaryIps(ec_id)
1423
    self._WriteConfig()
1424

    
1425
  def _EnsureUUID(self, item, ec_id):
1426
    """Ensures a given object has a valid UUID.
1427

1428
    @param item: the instance or node to be checked
1429
    @param ec_id: the execution context id for the uuid reservation
1430

1431
    """
1432
    if not item.uuid:
1433
      item.uuid = self._GenerateUniqueID(ec_id)
1434
    else:
1435
      self._CheckUniqueUUID(item, include_temporary=True)
1436

    
1437
  def _CheckUniqueUUID(self, item, include_temporary):
1438
    """Checks that the UUID of the given object is unique.
1439

1440
    @param item: the instance or node to be checked
1441
    @param include_temporary: whether temporarily generated UUID's should be
1442
              included in the check. If the UUID of the item to be checked is
1443
              a temporarily generated one, this has to be C{False}.
1444

1445
    """
1446
    if not item.uuid:
1447
      raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1448
    if item.uuid in self._AllIDs(include_temporary=include_temporary):
1449
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1450
                                      " in use" % (item.name, item.uuid))
1451

    
1452
  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1453
    """Set the instance's status to a given value.
1454

1455
    """
1456
    if inst_uuid not in self._config_data.instances:
1457
      raise errors.ConfigurationError("Unknown instance '%s'" %
1458
                                      inst_uuid)
1459
    instance = self._config_data.instances[inst_uuid]
1460

    
1461
    if status is None:
1462
      status = instance.admin_state
1463
    if disks_active is None:
1464
      disks_active = instance.disks_active
1465

    
1466
    assert status in constants.ADMINST_ALL, \
1467
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1468

    
1469
    if instance.admin_state != status or \
1470
       instance.disks_active != disks_active:
1471
      instance.admin_state = status
1472
      instance.disks_active = disks_active
1473
      instance.serial_no += 1
1474
      instance.mtime = time.time()
1475
      self._WriteConfig()
1476

    
1477
  @locking.ssynchronized(_config_lock)
1478
  def MarkInstanceUp(self, inst_uuid):
1479
    """Mark the instance status to up in the config.
1480

1481
    This also sets the instance disks active flag.
1482

1483
    """
1484
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1485

    
1486
  @locking.ssynchronized(_config_lock)
1487
  def MarkInstanceOffline(self, inst_uuid):
1488
    """Mark the instance status to down in the config.
1489

1490
    This also clears the instance disks active flag.
1491

1492
    """
1493
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1494

    
1495
  @locking.ssynchronized(_config_lock)
1496
  def RemoveInstance(self, inst_uuid):
1497
    """Remove the instance from the configuration.
1498

1499
    """
1500
    if inst_uuid not in self._config_data.instances:
1501
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1502

    
1503
    # If a network port has been allocated to the instance,
1504
    # return it to the pool of free ports.
1505
    inst = self._config_data.instances[inst_uuid]
1506
    network_port = getattr(inst, "network_port", None)
1507
    if network_port is not None:
1508
      self._config_data.cluster.tcpudp_port_pool.add(network_port)
1509

    
1510
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1511

    
1512
    for nic in instance.nics:
1513
      if nic.network and nic.ip:
1514
        # Return all IP addresses to the respective address pools
1515
        self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1516

    
1517
    del self._config_data.instances[inst_uuid]
1518
    self._config_data.cluster.serial_no += 1
1519
    self._WriteConfig()
1520

    
1521
  @locking.ssynchronized(_config_lock)
1522
  def RenameInstance(self, inst_uuid, new_name):
1523
    """Rename an instance.
1524

1525
    This needs to be done in ConfigWriter and not by RemoveInstance
1526
    combined with AddInstance as only we can guarantee an atomic
1527
    rename.
1528

1529
    """
1530
    if inst_uuid not in self._config_data.instances:
1531
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1532

    
1533
    inst = self._config_data.instances[inst_uuid]
1534
    inst.name = new_name
1535

    
1536
    for (idx, disk) in enumerate(inst.disks):
1537
      if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1538
        # rename the file paths in logical and physical id
1539
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1540
        disk.logical_id = (disk.logical_id[0],
1541
                           utils.PathJoin(file_storage_dir, inst.name,
1542
                                          "disk%s" % idx))
1543

    
1544
    # Force update of ssconf files
1545
    self._config_data.cluster.serial_no += 1
1546

    
1547
    self._WriteConfig()
1548

    
1549
  @locking.ssynchronized(_config_lock)
1550
  def MarkInstanceDown(self, inst_uuid):
1551
    """Mark the status of an instance to down in the configuration.
1552

1553
    This does not touch the instance disks active flag, as shut down instances
1554
    can still have active disks.
1555

1556
    """
1557
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1558

    
1559
  @locking.ssynchronized(_config_lock)
1560
  def MarkInstanceDisksActive(self, inst_uuid):
1561
    """Mark the status of instance disks active.
1562

1563
    """
1564
    self._SetInstanceStatus(inst_uuid, None, True)
1565

    
1566
  @locking.ssynchronized(_config_lock)
1567
  def MarkInstanceDisksInactive(self, inst_uuid):
1568
    """Mark the status of instance disks inactive.
1569

1570
    """
1571
    self._SetInstanceStatus(inst_uuid, None, False)
1572

    
1573
  def _UnlockedGetInstanceList(self):
1574
    """Get the list of instances.
1575

1576
    This function is for internal use, when the config lock is already held.
1577

1578
    """
1579
    return self._config_data.instances.keys()
1580

    
1581
  @locking.ssynchronized(_config_lock, shared=1)
1582
  def GetInstanceList(self):
1583
    """Get the list of instances.
1584

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

1587
    """
1588
    return self._UnlockedGetInstanceList()
1589

    
1590
  def ExpandInstanceName(self, short_name):
1591
    """Attempt to expand an incomplete instance name.
1592

1593
    """
1594
    # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1595
    all_insts = self.GetAllInstancesInfo().values()
1596
    expanded_name = _MatchNameComponentIgnoreCase(
1597
                      short_name, [inst.name for inst in all_insts])
1598

    
1599
    if expanded_name is not None:
1600
      # there has to be exactly one instance with that name
1601
      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1602
      return (inst.uuid, inst.name)
1603
    else:
1604
      return (None, None)
1605

    
1606
  def _UnlockedGetInstanceInfo(self, inst_uuid):
1607
    """Returns information about an instance.
1608

1609
    This function is for internal use, when the config lock is already held.
1610

1611
    """
1612
    if inst_uuid not in self._config_data.instances:
1613
      return None
1614

    
1615
    return self._config_data.instances[inst_uuid]
1616

    
1617
  @locking.ssynchronized(_config_lock, shared=1)
1618
  def GetInstanceInfo(self, inst_uuid):
1619
    """Returns information about an instance.
1620

1621
    It takes the information from the configuration file. Other information of
1622
    an instance are taken from the live systems.
1623

1624
    @param inst_uuid: UUID of the instance
1625

1626
    @rtype: L{objects.Instance}
1627
    @return: the instance object
1628

1629
    """
1630
    return self._UnlockedGetInstanceInfo(inst_uuid)
1631

    
1632
  @locking.ssynchronized(_config_lock, shared=1)
1633
  def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1634
    """Returns set of node group UUIDs for instance's nodes.
1635

1636
    @rtype: frozenset
1637

1638
    """
1639
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1640
    if not instance:
1641
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1642

    
1643
    if primary_only:
1644
      nodes = [instance.primary_node]
1645
    else:
1646
      nodes = instance.all_nodes
1647

    
1648
    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1649
                     for node_uuid in nodes)
1650

    
1651
  @locking.ssynchronized(_config_lock, shared=1)
1652
  def GetInstanceNetworks(self, inst_uuid):
1653
    """Returns set of network UUIDs for instance's nics.
1654

1655
    @rtype: frozenset
1656

1657
    """
1658
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1659
    if not instance:
1660
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1661

    
1662
    networks = set()
1663
    for nic in instance.nics:
1664
      if nic.network:
1665
        networks.add(nic.network)
1666

    
1667
    return frozenset(networks)
1668

    
1669
  @locking.ssynchronized(_config_lock, shared=1)
1670
  def GetMultiInstanceInfo(self, inst_uuids):
1671
    """Get the configuration of multiple instances.
1672

1673
    @param inst_uuids: list of instance UUIDs
1674
    @rtype: list
1675
    @return: list of tuples (instance UUID, instance_info), where
1676
        instance_info is what would GetInstanceInfo return for the
1677
        node, while keeping the original order
1678

1679
    """
1680
    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1681

    
1682
  @locking.ssynchronized(_config_lock, shared=1)
1683
  def GetMultiInstanceInfoByName(self, inst_names):
1684
    """Get the configuration of multiple instances.
1685

1686
    @param inst_names: list of instance names
1687
    @rtype: list
1688
    @return: list of tuples (instance, instance_info), where
1689
        instance_info is what would GetInstanceInfo return for the
1690
        node, while keeping the original order
1691

1692
    """
1693
    result = []
1694
    for name in inst_names:
1695
      instance = self._UnlockedGetInstanceInfoByName(name)
1696
      result.append((instance.uuid, instance))
1697
    return result
1698

    
1699
  @locking.ssynchronized(_config_lock, shared=1)
1700
  def GetAllInstancesInfo(self):
1701
    """Get the configuration of all instances.
1702

1703
    @rtype: dict
1704
    @return: dict of (instance, instance_info), where instance_info is what
1705
              would GetInstanceInfo return for the node
1706

1707
    """
1708
    return self._UnlockedGetAllInstancesInfo()
1709

    
1710
  def _UnlockedGetAllInstancesInfo(self):
1711
    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1712
                    for inst_uuid in self._UnlockedGetInstanceList()])
1713
    return my_dict
1714

    
1715
  @locking.ssynchronized(_config_lock, shared=1)
1716
  def GetInstancesInfoByFilter(self, filter_fn):
1717
    """Get instance configuration with a filter.
1718

1719
    @type filter_fn: callable
1720
    @param filter_fn: Filter function receiving instance object as parameter,
1721
      returning boolean. Important: this function is called while the
1722
      configuration locks is held. It must not do any complex work or call
1723
      functions potentially leading to a deadlock. Ideally it doesn't call any
1724
      other functions and just compares instance attributes.
1725

1726
    """
1727
    return dict((uuid, inst)
1728
                for (uuid, inst) in self._config_data.instances.items()
1729
                if filter_fn(inst))
1730

    
1731
  @locking.ssynchronized(_config_lock, shared=1)
1732
  def GetInstanceInfoByName(self, inst_name):
1733
    """Get the L{objects.Instance} object for a named instance.
1734

1735
    @param inst_name: name of the instance to get information for
1736
    @type inst_name: string
1737
    @return: the corresponding L{objects.Instance} instance or None if no
1738
          information is available
1739

1740
    """
1741
    return self._UnlockedGetInstanceInfoByName(inst_name)
1742

    
1743
  def _UnlockedGetInstanceInfoByName(self, inst_name):
1744
    for inst in self._UnlockedGetAllInstancesInfo().values():
1745
      if inst.name == inst_name:
1746
        return inst
1747
    return None
1748

    
1749
  def _UnlockedGetInstanceName(self, inst_uuid):
1750
    inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1751
    if inst_info is None:
1752
      raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1753
    return inst_info.name
1754

    
1755
  @locking.ssynchronized(_config_lock, shared=1)
1756
  def GetInstanceName(self, inst_uuid):
1757
    """Gets the instance name for the passed instance.
1758

1759
    @param inst_uuid: instance UUID to get name for
1760
    @type inst_uuid: string
1761
    @rtype: string
1762
    @return: instance name
1763

1764
    """
1765
    return self._UnlockedGetInstanceName(inst_uuid)
1766

    
1767
  @locking.ssynchronized(_config_lock, shared=1)
1768
  def GetInstanceNames(self, inst_uuids):
1769
    """Gets the instance names for the passed list of nodes.
1770

1771
    @param inst_uuids: list of instance UUIDs to get names for
1772
    @type inst_uuids: list of strings
1773
    @rtype: list of strings
1774
    @return: list of instance names
1775

1776
    """
1777
    return self._UnlockedGetInstanceNames(inst_uuids)
1778

    
1779
  def _UnlockedGetInstanceNames(self, inst_uuids):
1780
    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1781

    
1782
  @locking.ssynchronized(_config_lock)
1783
  def AddNode(self, node, ec_id):
1784
    """Add a node to the configuration.
1785

1786
    @type node: L{objects.Node}
1787
    @param node: a Node instance
1788

1789
    """
1790
    logging.info("Adding node %s to configuration", node.name)
1791

    
1792
    self._EnsureUUID(node, ec_id)
1793

    
1794
    node.serial_no = 1
1795
    node.ctime = node.mtime = time.time()
1796
    self._UnlockedAddNodeToGroup(node.uuid, node.group)
1797
    self._config_data.nodes[node.uuid] = node
1798
    self._config_data.cluster.serial_no += 1
1799
    self._WriteConfig()
1800

    
1801
  @locking.ssynchronized(_config_lock)
1802
  def RemoveNode(self, node_uuid):
1803
    """Remove a node from the configuration.
1804

1805
    """
1806
    logging.info("Removing node %s from configuration", node_uuid)
1807

    
1808
    if node_uuid not in self._config_data.nodes:
1809
      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1810

    
1811
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1812
    del self._config_data.nodes[node_uuid]
1813
    self._config_data.cluster.serial_no += 1
1814
    self._WriteConfig()
1815

    
1816
  def ExpandNodeName(self, short_name):
1817
    """Attempt to expand an incomplete node name into a node UUID.
1818

1819
    """
1820
    # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1821
    all_nodes = self.GetAllNodesInfo().values()
1822
    expanded_name = _MatchNameComponentIgnoreCase(
1823
                      short_name, [node.name for node in all_nodes])
1824

    
1825
    if expanded_name is not None:
1826
      # there has to be exactly one node with that name
1827
      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1828
      return (node.uuid, node.name)
1829
    else:
1830
      return (None, None)
1831

    
1832
  def _UnlockedGetNodeInfo(self, node_uuid):
1833
    """Get the configuration of a node, as stored in the config.
1834

1835
    This function is for internal use, when the config lock is already
1836
    held.
1837

1838
    @param node_uuid: the node UUID
1839

1840
    @rtype: L{objects.Node}
1841
    @return: the node object
1842

1843
    """
1844
    if node_uuid not in self._config_data.nodes:
1845
      return None
1846

    
1847
    return self._config_data.nodes[node_uuid]
1848

    
1849
  @locking.ssynchronized(_config_lock, shared=1)
1850
  def GetNodeInfo(self, node_uuid):
1851
    """Get the configuration of a node, as stored in the config.
1852

1853
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1854

1855
    @param node_uuid: the node UUID
1856

1857
    @rtype: L{objects.Node}
1858
    @return: the node object
1859

1860
    """
1861
    return self._UnlockedGetNodeInfo(node_uuid)
1862

    
1863
  @locking.ssynchronized(_config_lock, shared=1)
1864
  def GetNodeInstances(self, node_uuid):
1865
    """Get the instances of a node, as stored in the config.
1866

1867
    @param node_uuid: the node UUID
1868

1869
    @rtype: (list, list)
1870
    @return: a tuple with two lists: the primary and the secondary instances
1871

1872
    """
1873
    pri = []
1874
    sec = []
1875
    for inst in self._config_data.instances.values():
1876
      if inst.primary_node == node_uuid:
1877
        pri.append(inst.uuid)
1878
      if node_uuid in inst.secondary_nodes:
1879
        sec.append(inst.uuid)
1880
    return (pri, sec)
1881

    
1882
  @locking.ssynchronized(_config_lock, shared=1)
1883
  def GetNodeGroupInstances(self, uuid, primary_only=False):
1884
    """Get the instances of a node group.
1885

1886
    @param uuid: Node group UUID
1887
    @param primary_only: Whether to only consider primary nodes
1888
    @rtype: frozenset
1889
    @return: List of instance UUIDs in node group
1890

1891
    """
1892
    if primary_only:
1893
      nodes_fn = lambda inst: [inst.primary_node]
1894
    else:
1895
      nodes_fn = lambda inst: inst.all_nodes
1896

    
1897
    return frozenset(inst.uuid
1898
                     for inst in self._config_data.instances.values()
1899
                     for node_uuid in nodes_fn(inst)
1900
                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1901

    
1902
  def _UnlockedGetHvparamsString(self, hvname):
1903
    """Return the string representation of the list of hyervisor parameters of
1904
    the given hypervisor.
1905

1906
    @see: C{GetHvparams}
1907

1908
    """
1909
    result = ""
1910
    hvparams = self._config_data.cluster.hvparams[hvname]
1911
    for key in hvparams:
1912
      result += "%s=%s\n" % (key, hvparams[key])
1913
    return result
1914

    
1915
  @locking.ssynchronized(_config_lock, shared=1)
1916
  def GetHvparamsString(self, hvname):
1917
    """Return the hypervisor parameters of the given hypervisor.
1918

1919
    @type hvname: string
1920
    @param hvname: name of a hypervisor
1921
    @rtype: string
1922
    @return: string containing key-value-pairs, one pair on each line;
1923
      format: KEY=VALUE
1924

1925
    """
1926
    return self._UnlockedGetHvparamsString(hvname)
1927

    
1928
  def _UnlockedGetNodeList(self):
1929
    """Return the list of nodes which are in the configuration.
1930

1931
    This function is for internal use, when the config lock is already
1932
    held.
1933

1934
    @rtype: list
1935

1936
    """
1937
    return self._config_data.nodes.keys()
1938

    
1939
  @locking.ssynchronized(_config_lock, shared=1)
1940
  def GetNodeList(self):
1941
    """Return the list of nodes which are in the configuration.
1942

1943
    """
1944
    return self._UnlockedGetNodeList()
1945

    
1946
  def _UnlockedGetOnlineNodeList(self):
1947
    """Return the list of nodes which are online.
1948

1949
    """
1950
    all_nodes = [self._UnlockedGetNodeInfo(node)
1951
                 for node in self._UnlockedGetNodeList()]
1952
    return [node.uuid for node in all_nodes if not node.offline]
1953

    
1954
  @locking.ssynchronized(_config_lock, shared=1)
1955
  def GetOnlineNodeList(self):
1956
    """Return the list of nodes which are online.
1957

1958
    """
1959
    return self._UnlockedGetOnlineNodeList()
1960

    
1961
  @locking.ssynchronized(_config_lock, shared=1)
1962
  def GetVmCapableNodeList(self):
1963
    """Return the list of nodes which are not vm capable.
1964

1965
    """
1966
    all_nodes = [self._UnlockedGetNodeInfo(node)
1967
                 for node in self._UnlockedGetNodeList()]
1968
    return [node.uuid for node in all_nodes if node.vm_capable]
1969

    
1970
  @locking.ssynchronized(_config_lock, shared=1)
1971
  def GetNonVmCapableNodeList(self):
1972
    """Return the list of nodes which are not vm capable.
1973

1974
    """
1975
    all_nodes = [self._UnlockedGetNodeInfo(node)
1976
                 for node in self._UnlockedGetNodeList()]
1977
    return [node.uuid for node in all_nodes if not node.vm_capable]
1978

    
1979
  @locking.ssynchronized(_config_lock, shared=1)
1980
  def GetMultiNodeInfo(self, node_uuids):
1981
    """Get the configuration of multiple nodes.
1982

1983
    @param node_uuids: list of node UUIDs
1984
    @rtype: list
1985
    @return: list of tuples of (node, node_info), where node_info is
1986
        what would GetNodeInfo return for the node, in the original
1987
        order
1988

1989
    """
1990
    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
1991

    
1992
  def _UnlockedGetAllNodesInfo(self):
1993
    """Gets configuration of all nodes.
1994

1995
    @note: See L{GetAllNodesInfo}
1996

1997
    """
1998
    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
1999
                 for node_uuid in self._UnlockedGetNodeList()])
2000

    
2001
  @locking.ssynchronized(_config_lock, shared=1)
2002
  def GetAllNodesInfo(self):
2003
    """Get the configuration of all nodes.
2004

2005
    @rtype: dict
2006
    @return: dict of (node, node_info), where node_info is what
2007
              would GetNodeInfo return for the node
2008

2009
    """
2010
    return self._UnlockedGetAllNodesInfo()
2011

    
2012
  def _UnlockedGetNodeInfoByName(self, node_name):
2013
    for node in self._UnlockedGetAllNodesInfo().values():
2014
      if node.name == node_name:
2015
        return node
2016
    return None
2017

    
2018
  @locking.ssynchronized(_config_lock, shared=1)
2019
  def GetNodeInfoByName(self, node_name):
2020
    """Get the L{objects.Node} object for a named node.
2021

2022
    @param node_name: name of the node to get information for
2023
    @type node_name: string
2024
    @return: the corresponding L{objects.Node} instance or None if no
2025
          information is available
2026

2027
    """
2028
    return self._UnlockedGetNodeInfoByName(node_name)
2029

    
2030
  def _UnlockedGetNodeName(self, node_spec):
2031
    if isinstance(node_spec, objects.Node):
2032
      return node_spec.name
2033
    elif isinstance(node_spec, basestring):
2034
      node_info = self._UnlockedGetNodeInfo(node_spec)
2035
      if node_info is None:
2036
        raise errors.OpExecError("Unknown node: %s" % node_spec)
2037
      return node_info.name
2038
    else:
2039
      raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2040

    
2041
  @locking.ssynchronized(_config_lock, shared=1)
2042
  def GetNodeName(self, node_spec):
2043
    """Gets the node name for the passed node.
2044

2045
    @param node_spec: node to get names for
2046
    @type node_spec: either node UUID or a L{objects.Node} object
2047
    @rtype: string
2048
    @return: node name
2049

2050
    """
2051
    return self._UnlockedGetNodeName(node_spec)
2052

    
2053
  def _UnlockedGetNodeNames(self, node_specs):
2054
    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2055

    
2056
  @locking.ssynchronized(_config_lock, shared=1)
2057
  def GetNodeNames(self, node_specs):
2058
    """Gets the node names for the passed list of nodes.
2059

2060
    @param node_specs: list of nodes to get names for
2061
    @type node_specs: list of either node UUIDs or L{objects.Node} objects
2062
    @rtype: list of strings
2063
    @return: list of node names
2064

2065
    """
2066
    return self._UnlockedGetNodeNames(node_specs)
2067

    
2068
  @locking.ssynchronized(_config_lock, shared=1)
2069
  def GetNodeGroupsFromNodes(self, node_uuids):
2070
    """Returns groups for a list of nodes.
2071

2072
    @type node_uuids: list of string
2073
    @param node_uuids: List of node UUIDs
2074
    @rtype: frozenset
2075

2076
    """
2077
    return frozenset(self._UnlockedGetNodeInfo(uuid).group
2078
                     for uuid in node_uuids)
2079

    
2080
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2081
    """Get the number of current and maximum desired and possible candidates.
2082

2083
    @type exceptions: list
2084
    @param exceptions: if passed, list of nodes that should be ignored
2085
    @rtype: tuple
2086
    @return: tuple of (current, desired and possible, possible)
2087

2088
    """
2089
    mc_now = mc_should = mc_max = 0
2090
    for node in self._config_data.nodes.values():
2091
      if exceptions and node.uuid in exceptions:
2092
        continue
2093
      if not (node.offline or node.drained) and node.master_capable:
2094
        mc_max += 1
2095
      if node.master_candidate:
2096
        mc_now += 1
2097
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2098
    return (mc_now, mc_should, mc_max)
2099

    
2100
  @locking.ssynchronized(_config_lock, shared=1)
2101
  def GetMasterCandidateStats(self, exceptions=None):
2102
    """Get the number of current and maximum possible candidates.
2103

2104
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2105

2106
    @type exceptions: list
2107
    @param exceptions: if passed, list of nodes that should be ignored
2108
    @rtype: tuple
2109
    @return: tuple of (current, max)
2110

2111
    """
2112
    return self._UnlockedGetMasterCandidateStats(exceptions)
2113

    
2114
  @locking.ssynchronized(_config_lock)
2115
  def MaintainCandidatePool(self, exception_node_uuids):
2116
    """Try to grow the candidate pool to the desired size.
2117

2118
    @type exception_node_uuids: list
2119
    @param exception_node_uuids: if passed, list of nodes that should be ignored
2120
    @rtype: list
2121
    @return: list with the adjusted nodes (L{objects.Node} instances)
2122

2123
    """
2124
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2125
                          exception_node_uuids)
2126
    mod_list = []
2127
    if mc_now < mc_max:
2128
      node_list = self._config_data.nodes.keys()
2129
      random.shuffle(node_list)
2130
      for uuid in node_list:
2131
        if mc_now >= mc_max:
2132
          break
2133
        node = self._config_data.nodes[uuid]
2134
        if (node.master_candidate or node.offline or node.drained or
2135
            node.uuid in exception_node_uuids or not node.master_capable):
2136
          continue
2137
        mod_list.append(node)
2138
        node.master_candidate = True
2139
        node.serial_no += 1
2140
        mc_now += 1
2141
      if mc_now != mc_max:
2142
        # this should not happen
2143
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
2144
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
2145
      if mod_list:
2146
        self._config_data.cluster.serial_no += 1
2147
        self._WriteConfig()
2148

    
2149
    return mod_list
2150

    
2151
  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2152
    """Add a given node to the specified group.
2153

2154
    """
2155
    if nodegroup_uuid not in self._config_data.nodegroups:
2156
      # This can happen if a node group gets deleted between its lookup and
2157
      # when we're adding the first node to it, since we don't keep a lock in
2158
      # the meantime. It's ok though, as we'll fail cleanly if the node group
2159
      # is not found anymore.
2160
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2161
    if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2162
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2163

    
2164
  def _UnlockedRemoveNodeFromGroup(self, node):
2165
    """Remove a given node from its group.
2166

2167
    """
2168
    nodegroup = node.group
2169
    if nodegroup not in self._config_data.nodegroups:
2170
      logging.warning("Warning: node '%s' has unknown node group '%s'"
2171
                      " (while being removed from it)", node.uuid, nodegroup)
2172
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
2173
    if node.uuid not in nodegroup_obj.members:
2174
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
2175
                      " (while being removed from it)", node.uuid, nodegroup)
2176
    else:
2177
      nodegroup_obj.members.remove(node.uuid)
2178

    
2179
  @locking.ssynchronized(_config_lock)
2180
  def AssignGroupNodes(self, mods):
2181
    """Changes the group of a number of nodes.
2182

2183
    @type mods: list of tuples; (node name, new group UUID)
2184
    @param mods: Node membership modifications
2185

2186
    """
2187
    groups = self._config_data.nodegroups
2188
    nodes = self._config_data.nodes
2189

    
2190
    resmod = []
2191

    
2192
    # Try to resolve UUIDs first
2193
    for (node_uuid, new_group_uuid) in mods:
2194
      try:
2195
        node = nodes[node_uuid]
2196
      except KeyError:
2197
        raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2198

    
2199
      if node.group == new_group_uuid:
2200
        # Node is being assigned to its current group
2201
        logging.debug("Node '%s' was assigned to its current group (%s)",
2202
                      node_uuid, node.group)
2203
        continue
2204

    
2205
      # Try to find current group of node
2206
      try:
2207
        old_group = groups[node.group]
2208
      except KeyError:
2209
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2210
                                        node.group)
2211

    
2212
      # Try to find new group for node
2213
      try:
2214
        new_group = groups[new_group_uuid]
2215
      except KeyError:
2216
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2217
                                        new_group_uuid)
2218

    
2219
      assert node.uuid in old_group.members, \
2220
        ("Inconsistent configuration: node '%s' not listed in members for its"
2221
         " old group '%s'" % (node.uuid, old_group.uuid))
2222
      assert node.uuid not in new_group.members, \
2223
        ("Inconsistent configuration: node '%s' already listed in members for"
2224
         " its new group '%s'" % (node.uuid, new_group.uuid))
2225

    
2226
      resmod.append((node, old_group, new_group))
2227

    
2228
    # Apply changes
2229
    for (node, old_group, new_group) in resmod:
2230
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2231
        "Assigning to current group is not possible"
2232

    
2233
      node.group = new_group.uuid
2234

    
2235
      # Update members of involved groups
2236
      if node.uuid in old_group.members:
2237
        old_group.members.remove(node.uuid)
2238
      if node.uuid not in new_group.members:
2239
        new_group.members.append(node.uuid)
2240

    
2241
    # Update timestamps and serials (only once per node/group object)
2242
    now = time.time()
2243
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2244
      obj.serial_no += 1
2245
      obj.mtime = now
2246

    
2247
    # Force ssconf update
2248
    self._config_data.cluster.serial_no += 1
2249

    
2250
    self._WriteConfig()
2251

    
2252
  def _BumpSerialNo(self):
2253
    """Bump up the serial number of the config.
2254

2255
    """
2256
    self._config_data.serial_no += 1
2257
    self._config_data.mtime = time.time()
2258

    
2259
  def _AllUUIDObjects(self):
2260
    """Returns all objects with uuid attributes.
2261

2262
    """
2263
    return (self._config_data.instances.values() +
2264
            self._config_data.nodes.values() +
2265
            self._config_data.nodegroups.values() +
2266
            self._config_data.networks.values() +
2267
            self._AllDisks() +
2268
            self._AllNICs() +
2269
            [self._config_data.cluster])
2270

    
2271
  def _OpenConfig(self, accept_foreign):
2272
    """Read the config data from disk.
2273

2274
    """
2275
    raw_data = utils.ReadFile(self._cfg_file)
2276

    
2277
    try:
2278
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2279
    except Exception, err:
2280
      raise errors.ConfigurationError(err)
2281

    
2282
    # Make sure the configuration has the right version
2283
    _ValidateConfig(data)
2284

    
2285
    if (not hasattr(data, "cluster") or
2286
        not hasattr(data.cluster, "rsahostkeypub")):
2287
      raise errors.ConfigurationError("Incomplete configuration"
2288
                                      " (missing cluster.rsahostkeypub)")
2289

    
2290
    if not data.cluster.master_node in data.nodes:
2291
      msg = ("The configuration denotes node %s as master, but does not"
2292
             " contain information about this node" %
2293
             data.cluster.master_node)
2294
      raise errors.ConfigurationError(msg)
2295

    
2296
    master_info = data.nodes[data.cluster.master_node]
2297
    if master_info.name != self._my_hostname and not accept_foreign:
2298
      msg = ("The configuration denotes node %s as master, while my"
2299
             " hostname is %s; opening a foreign configuration is only"
2300
             " possible in accept_foreign mode" %
2301
             (master_info.name, self._my_hostname))
2302
      raise errors.ConfigurationError(msg)
2303

    
2304
    self._config_data = data
2305
    # reset the last serial as -1 so that the next write will cause
2306
    # ssconf update
2307
    self._last_cluster_serial = -1
2308

    
2309
    # Upgrade configuration if needed
2310
    self._UpgradeConfig()
2311

    
2312
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2313

    
2314
  def _UpgradeConfig(self):
2315
    """Run any upgrade steps.
2316

2317
    This method performs both in-object upgrades and also update some data
2318
    elements that need uniqueness across the whole configuration or interact
2319
    with other objects.
2320

2321
    @warning: this function will call L{_WriteConfig()}, but also
2322
        L{DropECReservations} so it needs to be called only from a
2323
        "safe" place (the constructor). If one wanted to call it with
2324
        the lock held, a DropECReservationUnlocked would need to be
2325
        created first, to avoid causing deadlock.
2326

2327
    """
2328
    # Keep a copy of the persistent part of _config_data to check for changes
2329
    # Serialization doesn't guarantee order in dictionaries
2330
    oldconf = copy.deepcopy(self._config_data.ToDict())
2331

    
2332
    # In-object upgrades
2333
    self._config_data.UpgradeConfig()
2334

    
2335
    for item in self._AllUUIDObjects():
2336
      if item.uuid is None:
2337
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2338
    if not self._config_data.nodegroups:
2339
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2340
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2341
                                            members=[])
2342
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2343
    for node in self._config_data.nodes.values():
2344
      if not node.group:
2345
        node.group = self.LookupNodeGroup(None)
2346
      # This is technically *not* an upgrade, but needs to be done both when
2347
      # nodegroups are being added, and upon normally loading the config,
2348
      # because the members list of a node group is discarded upon
2349
      # serializing/deserializing the object.
2350
      self._UnlockedAddNodeToGroup(node.uuid, node.group)
2351

    
2352
    modified = (oldconf != self._config_data.ToDict())
2353
    if modified:
2354
      self._WriteConfig()
2355
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2356
      # only called at config init time, without the lock held
2357
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2358
    else:
2359
      config_errors = self._UnlockedVerifyConfig()
2360
      if config_errors:
2361
        errmsg = ("Loaded configuration data is not consistent: %s" %
2362
                  (utils.CommaJoin(config_errors)))
2363
        logging.critical(errmsg)
2364

    
2365
  def _DistributeConfig(self, feedback_fn):
2366
    """Distribute the configuration to the other nodes.
2367

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

2371
    """
2372
    if self._offline:
2373
      return True
2374

    
2375
    bad = False
2376

    
2377
    node_list = []
2378
    addr_list = []
2379
    myhostname = self._my_hostname
2380
    # we can skip checking whether _UnlockedGetNodeInfo returns None
2381
    # since the node list comes from _UnlocketGetNodeList, and we are
2382
    # called with the lock held, so no modifications should take place
2383
    # in between
2384
    for node_uuid in self._UnlockedGetNodeList():
2385
      node_info = self._UnlockedGetNodeInfo(node_uuid)
2386
      if node_info.name == myhostname or not node_info.master_candidate:
2387
        continue
2388
      node_list.append(node_info.name)
2389
      addr_list.append(node_info.primary_ip)
2390

    
2391
    # TODO: Use dedicated resolver talking to config writer for name resolution
2392
    result = \
2393
      self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2394
    for to_node, to_result in result.items():
2395
      msg = to_result.fail_msg
2396
      if msg:
2397
        msg = ("Copy of file %s to node %s failed: %s" %
2398
               (self._cfg_file, to_node, msg))
2399
        logging.error(msg)
2400

    
2401
        if feedback_fn:
2402
          feedback_fn(msg)
2403

    
2404
        bad = True
2405

    
2406
    return not bad
2407

    
2408
  def _WriteConfig(self, destination=None, feedback_fn=None):
2409
    """Write the configuration data to persistent storage.
2410

2411
    """
2412
    assert feedback_fn is None or callable(feedback_fn)
2413

    
2414
    # Warn on config errors, but don't abort the save - the
2415
    # configuration has already been modified, and we can't revert;
2416
    # the best we can do is to warn the user and save as is, leaving
2417
    # recovery to the user
2418
    config_errors = self._UnlockedVerifyConfig()
2419
    if config_errors:
2420
      errmsg = ("Configuration data is not consistent: %s" %
2421
                (utils.CommaJoin(config_errors)))
2422
      logging.critical(errmsg)
2423
      if feedback_fn:
2424
        feedback_fn(errmsg)
2425

    
2426
    if destination is None:
2427
      destination = self._cfg_file
2428
    self._BumpSerialNo()
2429
    txt = serializer.Dump(self._config_data.ToDict())
2430

    
2431
    getents = self._getents()
2432
    try:
2433
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2434
                               close=False, gid=getents.confd_gid, mode=0640)
2435
    except errors.LockError:
2436
      raise errors.ConfigurationError("The configuration file has been"
2437
                                      " modified since the last write, cannot"
2438
                                      " update")
2439
    try:
2440
      self._cfg_id = utils.GetFileID(fd=fd)
2441
    finally:
2442
      os.close(fd)
2443

    
2444
    self.write_count += 1
2445

    
2446
    # and redistribute the config file to master candidates
2447
    self._DistributeConfig(feedback_fn)
2448

    
2449
    # Write ssconf files on all nodes (including locally)
2450
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
2451
      if not self._offline:
2452
        result = self._GetRpc(None).call_write_ssconf_files(
2453
          self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2454
          self._UnlockedGetSsconfValues())
2455

    
2456
        for nname, nresu in result.items():
2457
          msg = nresu.fail_msg
2458
          if msg:
2459
            errmsg = ("Error while uploading ssconf files to"
2460
                      " node %s: %s" % (nname, msg))
2461
            logging.warning(errmsg)
2462

    
2463
            if feedback_fn:
2464
              feedback_fn(errmsg)
2465

    
2466
      self._last_cluster_serial = self._config_data.cluster.serial_no
2467

    
2468
  def _GetAllHvparamsStrings(self, hypervisors):
2469
    """Get the hvparams of all given hypervisors from the config.
2470

2471
    @type hypervisors: list of string
2472
    @param hypervisors: list of hypervisor names
2473
    @rtype: dict of strings
2474
    @returns: dictionary mapping the hypervisor name to a string representation
2475
      of the hypervisor's hvparams
2476

2477
    """
2478
    hvparams = {}
2479
    for hv in hypervisors:
2480
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2481
    return hvparams
2482

    
2483
  @staticmethod
2484
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2485
    """Extends the ssconf_values dictionary by hvparams.
2486

2487
    @type ssconf_values: dict of strings
2488
    @param ssconf_values: dictionary mapping ssconf_keys to strings
2489
      representing the content of ssconf files
2490
    @type all_hvparams: dict of strings
2491
    @param all_hvparams: dictionary mapping hypervisor names to a string
2492
      representation of their hvparams
2493
    @rtype: same as ssconf_values
2494
    @returns: the ssconf_values dictionary extended by hvparams
2495

2496
    """
2497
    for hv in all_hvparams:
2498
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2499
      ssconf_values[ssconf_key] = all_hvparams[hv]
2500
    return ssconf_values
2501

    
2502
  def _UnlockedGetSsconfValues(self):
2503
    """Return the values needed by ssconf.
2504

2505
    @rtype: dict
2506
    @return: a dictionary with keys the ssconf names and values their
2507
        associated value
2508

2509
    """
2510
    fn = "\n".join
2511
    instance_names = utils.NiceSort(
2512
                       [inst.name for inst in
2513
                        self._UnlockedGetAllInstancesInfo().values()])
2514
    node_infos = self._UnlockedGetAllNodesInfo().values()
2515
    node_names = [node.name for node in node_infos]
2516
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2517
                    for ninfo in node_infos]
2518
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2519
                    for ninfo in node_infos]
2520

    
2521
    instance_data = fn(instance_names)
2522
    off_data = fn(node.name for node in node_infos if node.offline)
2523
    on_data = fn(node.name for node in node_infos if not node.offline)
2524
    mc_data = fn(node.name for node in node_infos if node.master_candidate)
2525
    mc_ips_data = fn(node.primary_ip for node in node_infos
2526
                     if node.master_candidate)
2527
    node_data = fn(node_names)
2528
    node_pri_ips_data = fn(node_pri_ips)
2529
    node_snd_ips_data = fn(node_snd_ips)
2530

    
2531
    cluster = self._config_data.cluster
2532
    cluster_tags = fn(cluster.GetTags())
2533

    
2534
    hypervisor_list = fn(cluster.enabled_hypervisors)
2535
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2536

    
2537
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2538

    
2539
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2540
                  self._config_data.nodegroups.values()]
2541
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2542
    networks = ["%s %s" % (net.uuid, net.name) for net in
2543
                self._config_data.networks.values()]
2544
    networks_data = fn(utils.NiceSort(networks))
2545

    
2546
    ssconf_values = {
2547
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
2548
      constants.SS_CLUSTER_TAGS: cluster_tags,
2549
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2550
      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2551
      constants.SS_MASTER_CANDIDATES: mc_data,
2552
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2553
      constants.SS_MASTER_IP: cluster.master_ip,
2554
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
2555
      constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2556
      constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2557
      constants.SS_NODE_LIST: node_data,
2558
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2559
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2560
      constants.SS_OFFLINE_NODES: off_data,
2561
      constants.SS_ONLINE_NODES: on_data,
2562
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2563
      constants.SS_INSTANCE_LIST: instance_data,
2564
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2565
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
2566
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2567
      constants.SS_UID_POOL: uid_pool,
2568
      constants.SS_NODEGROUPS: nodegroups_data,
2569
      constants.SS_NETWORKS: networks_data,
2570
      }
2571
    ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2572
                                                     all_hvparams)
2573
    bad_values = [(k, v) for k, v in ssconf_values.items()
2574
                  if not isinstance(v, (str, basestring))]
2575
    if bad_values:
2576
      err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2577
      raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2578
                                      " values: %s" % err)
2579
    return ssconf_values
2580

    
2581
  @locking.ssynchronized(_config_lock, shared=1)
2582
  def GetSsconfValues(self):
2583
    """Wrapper using lock around _UnlockedGetSsconf().
2584

2585
    """
2586
    return self._UnlockedGetSsconfValues()
2587

    
2588
  @locking.ssynchronized(_config_lock, shared=1)
2589
  def GetVGName(self):
2590
    """Return the volume group name.
2591

2592
    """
2593
    return self._config_data.cluster.volume_group_name
2594

    
2595
  @locking.ssynchronized(_config_lock)
2596
  def SetVGName(self, vg_name):
2597
    """Set the volume group name.
2598

2599
    """
2600
    self._config_data.cluster.volume_group_name = vg_name
2601
    self._config_data.cluster.serial_no += 1
2602
    self._WriteConfig()
2603

    
2604
  @locking.ssynchronized(_config_lock, shared=1)
2605
  def GetDRBDHelper(self):
2606
    """Return DRBD usermode helper.
2607

2608
    """
2609
    return self._config_data.cluster.drbd_usermode_helper
2610

    
2611
  @locking.ssynchronized(_config_lock)
2612
  def SetDRBDHelper(self, drbd_helper):
2613
    """Set DRBD usermode helper.
2614

2615
    """
2616
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2617
    self._config_data.cluster.serial_no += 1
2618
    self._WriteConfig()
2619

    
2620
  @locking.ssynchronized(_config_lock, shared=1)
2621
  def GetMACPrefix(self):
2622
    """Return the mac prefix.
2623

2624
    """
2625
    return self._config_data.cluster.mac_prefix
2626

    
2627
  @locking.ssynchronized(_config_lock, shared=1)
2628
  def GetClusterInfo(self):
2629
    """Returns information about the cluster
2630

2631
    @rtype: L{objects.Cluster}
2632
    @return: the cluster object
2633

2634
    """
2635
    return self._config_data.cluster
2636

    
2637
  @locking.ssynchronized(_config_lock, shared=1)
2638
  def HasAnyDiskOfType(self, dev_type):
2639
    """Check if in there is at disk of the given type in the configuration.
2640

2641
    """
2642
    return self._config_data.HasAnyDiskOfType(dev_type)
2643

    
2644
  @locking.ssynchronized(_config_lock)
2645
  def Update(self, target, feedback_fn, ec_id=None):
2646
    """Notify function to be called after updates.
2647

2648
    This function must be called when an object (as returned by
2649
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2650
    caller wants the modifications saved to the backing store. Note
2651
    that all modified objects will be saved, but the target argument
2652
    is the one the caller wants to ensure that it's saved.
2653

2654
    @param target: an instance of either L{objects.Cluster},
2655
        L{objects.Node} or L{objects.Instance} which is existing in
2656
        the cluster
2657
    @param feedback_fn: Callable feedback function
2658

2659
    """
2660
    if self._config_data is None:
2661
      raise errors.ProgrammerError("Configuration file not read,"
2662
                                   " cannot save.")
2663
    update_serial = False
2664
    if isinstance(target, objects.Cluster):
2665
      test = target == self._config_data.cluster
2666
    elif isinstance(target, objects.Node):
2667
      test = target in self._config_data.nodes.values()
2668
      update_serial = True
2669
    elif isinstance(target, objects.Instance):
2670
      test = target in self._config_data.instances.values()
2671
    elif isinstance(target, objects.NodeGroup):
2672
      test = target in self._config_data.nodegroups.values()
2673
    elif isinstance(target, objects.Network):
2674
      test = target in self._config_data.networks.values()
2675
    else:
2676
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2677
                                   " ConfigWriter.Update" % type(target))
2678
    if not test:
2679
      raise errors.ConfigurationError("Configuration updated since object"
2680
                                      " has been read or unknown object")
2681
    target.serial_no += 1
2682
    target.mtime = now = time.time()
2683

    
2684
    if update_serial:
2685
      # for node updates, we need to increase the cluster serial too
2686
      self._config_data.cluster.serial_no += 1
2687
      self._config_data.cluster.mtime = now
2688

    
2689
    if isinstance(target, objects.Instance):
2690
      self._UnlockedReleaseDRBDMinors(target.uuid)
2691

    
2692
    if ec_id is not None:
2693
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2694
      self._UnlockedCommitTemporaryIps(ec_id)
2695

    
2696
    self._WriteConfig(feedback_fn=feedback_fn)
2697

    
2698
  @locking.ssynchronized(_config_lock)
2699
  def DropECReservations(self, ec_id):
2700
    """Drop per-execution-context reservations
2701

2702
    """
2703
    for rm in self._all_rms:
2704
      rm.DropECReservations(ec_id)
2705

    
2706
  @locking.ssynchronized(_config_lock, shared=1)
2707
  def GetAllNetworksInfo(self):
2708
    """Get configuration info of all the networks.
2709

2710
    """
2711
    return dict(self._config_data.networks)
2712

    
2713
  def _UnlockedGetNetworkList(self):
2714
    """Get the list of networks.
2715

2716
    This function is for internal use, when the config lock is already held.
2717

2718
    """
2719
    return self._config_data.networks.keys()
2720

    
2721
  @locking.ssynchronized(_config_lock, shared=1)
2722
  def GetNetworkList(self):
2723
    """Get the list of networks.
2724

2725
    @return: array of networks, ex. ["main", "vlan100", "200]
2726

2727
    """
2728
    return self._UnlockedGetNetworkList()
2729

    
2730
  @locking.ssynchronized(_config_lock, shared=1)
2731
  def GetNetworkNames(self):
2732
    """Get a list of network names
2733

2734
    """
2735
    names = [net.name
2736
             for net in self._config_data.networks.values()]
2737
    return names
2738

    
2739
  def _UnlockedGetNetwork(self, uuid):
2740
    """Returns information about a network.
2741

2742
    This function is for internal use, when the config lock is already held.
2743

2744
    """
2745
    if uuid not in self._config_data.networks:
2746
      return None
2747

    
2748
    return self._config_data.networks[uuid]
2749

    
2750
  @locking.ssynchronized(_config_lock, shared=1)
2751
  def GetNetwork(self, uuid):
2752
    """Returns information about a network.
2753

2754
    It takes the information from the configuration file.
2755

2756
    @param uuid: UUID of the network
2757

2758
    @rtype: L{objects.Network}
2759
    @return: the network object
2760

2761
    """
2762
    return self._UnlockedGetNetwork(uuid)
2763

    
2764
  @locking.ssynchronized(_config_lock)
2765
  def AddNetwork(self, net, ec_id, check_uuid=True):
2766
    """Add a network to the configuration.
2767

2768
    @type net: L{objects.Network}
2769
    @param net: the Network object to add
2770
    @type ec_id: string
2771
    @param ec_id: unique id for the job to use when creating a missing UUID
2772

2773
    """
2774
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2775
    self._WriteConfig()
2776

    
2777
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2778
    """Add a network to the configuration.
2779

2780
    """
2781
    logging.info("Adding network %s to configuration", net.name)
2782

    
2783
    if check_uuid:
2784
      self._EnsureUUID(net, ec_id)
2785

    
2786
    net.serial_no = 1
2787
    net.ctime = net.mtime = time.time()
2788
    self._config_data.networks[net.uuid] = net
2789
    self._config_data.cluster.serial_no += 1
2790

    
2791
  def _UnlockedLookupNetwork(self, target):
2792
    """Lookup a network's UUID.
2793

2794
    @type target: string
2795
    @param target: network name or UUID
2796
    @rtype: string
2797
    @return: network UUID
2798
    @raises errors.OpPrereqError: when the target network cannot be found
2799

2800
    """
2801
    if target is None:
2802
      return None
2803
    if target in self._config_data.networks:
2804
      return target
2805
    for net in self._config_data.networks.values():
2806
      if net.name == target:
2807
        return net.uuid
2808
    raise errors.OpPrereqError("Network '%s' not found" % target,
2809
                               errors.ECODE_NOENT)
2810

    
2811
  @locking.ssynchronized(_config_lock, shared=1)
2812
  def LookupNetwork(self, target):
2813
    """Lookup a network's UUID.
2814

2815
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2816

2817
    @type target: string
2818
    @param target: network name or UUID
2819
    @rtype: string
2820
    @return: network UUID
2821

2822
    """
2823
    return self._UnlockedLookupNetwork(target)
2824

    
2825
  @locking.ssynchronized(_config_lock)
2826
  def RemoveNetwork(self, network_uuid):
2827
    """Remove a network from the configuration.
2828

2829
    @type network_uuid: string
2830
    @param network_uuid: the UUID of the network to remove
2831

2832
    """
2833
    logging.info("Removing network %s from configuration", network_uuid)
2834

    
2835
    if network_uuid not in self._config_data.networks:
2836
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2837

    
2838
    del self._config_data.networks[network_uuid]
2839
    self._config_data.cluster.serial_no += 1
2840
    self._WriteConfig()
2841

    
2842
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2843
    """Get the netparams (mode, link) of a network.
2844

2845
    Get a network's netparams for a given node.
2846

2847
    @type net_uuid: string
2848
    @param net_uuid: network uuid
2849
    @type node_uuid: string
2850
    @param node_uuid: node UUID
2851
    @rtype: dict or None
2852
    @return: netparams
2853

2854
    """
2855
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2856
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2857
    netparams = nodegroup_info.networks.get(net_uuid, None)
2858

    
2859
    return netparams
2860

    
2861
  @locking.ssynchronized(_config_lock, shared=1)
2862
  def GetGroupNetParams(self, net_uuid, node_uuid):
2863
    """Locking wrapper of _UnlockedGetGroupNetParams()
2864

2865
    """
2866
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2867

    
2868
  @locking.ssynchronized(_config_lock, shared=1)
2869
  def CheckIPInNodeGroup(self, ip, node_uuid):
2870
    """Check IP uniqueness in nodegroup.
2871

2872
    Check networks that are connected in the node's node group
2873
    if ip is contained in any of them. Used when creating/adding
2874
    a NIC to ensure uniqueness among nodegroups.
2875

2876
    @type ip: string
2877
    @param ip: ip address
2878
    @type node_uuid: string
2879
    @param node_uuid: node UUID
2880
    @rtype: (string, dict) or (None, None)
2881
    @return: (network name, netparams)
2882

2883
    """
2884
    if ip is None:
2885
      return (None, None)
2886
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2887
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2888
    for net_uuid in nodegroup_info.networks.keys():
2889
      net_info = self._UnlockedGetNetwork(net_uuid)
2890
      pool = network.AddressPool(net_info)
2891
      if pool.Contains(ip):
2892
        return (net_info.name, nodegroup_info.networks[net_uuid])
2893

    
2894
    return (None, None)