Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 690e509d

History | View | Annotate | Download (94.5 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):
388
    """Reserve a given IPv4 address for use by an instance.
389

390
    """
391
    nobj = self._UnlockedGetNetwork(net_uuid)
392
    pool = network.AddressPool(nobj)
393
    try:
394
      isreserved = pool.IsReserved(address)
395
    except errors.AddressPoolError:
396
      raise errors.ReservationError("IP address not in network")
397
    if isreserved:
398
      raise errors.ReservationError("IP address already in use")
399

    
400
    return self._temporary_ips.Reserve(ec_id,
401
                                       (constants.RESERVE_ACTION,
402
                                        address, net_uuid))
403

    
404
  @locking.ssynchronized(_config_lock, shared=1)
405
  def ReserveIp(self, net_uuid, address, ec_id):
406
    """Reserve a given IPv4 address for use by an instance.
407

408
    """
409
    if net_uuid:
410
      return self._UnlockedReserveIp(net_uuid, address, ec_id)
411

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

416
    @type lv_name: string
417
    @param lv_name: the logical volume name to reserve
418

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

    
426
  @locking.ssynchronized(_config_lock, shared=1)
427
  def GenerateDRBDSecret(self, ec_id):
428
    """Generate a DRBD secret.
429

430
    This checks the current disks for duplicates.
431

432
    """
433
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
434
                                            utils.GenerateSecret,
435
                                            ec_id)
436

    
437
  def _AllLVs(self):
438
    """Compute the list of all LVs.
439

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

    
448
  def _AllDisks(self):
449
    """Compute the list of all Disks (recursively, including children).
450

451
    """
452
    def DiskAndAllChildren(disk):
453
      """Returns a list containing the given disk and all of his children.
454

455
      """
456
      disks = [disk]
457
      if disk.children:
458
        for child_disk in disk.children:
459
          disks.extend(DiskAndAllChildren(child_disk))
460
      return disks
461

    
462
    disks = []
463
    for instance in self._config_data.instances.values():
464
      for disk in instance.disks:
465
        disks.extend(DiskAndAllChildren(disk))
466
    return disks
467

    
468
  def _AllNICs(self):
469
    """Compute the list of all NICs.
470

471
    """
472
    nics = []
473
    for instance in self._config_data.instances.values():
474
      nics.extend(instance.nics)
475
    return nics
476

    
477
  def _AllIDs(self, include_temporary):
478
    """Compute the list of all UUIDs and names we have.
479

480
    @type include_temporary: boolean
481
    @param include_temporary: whether to include the _temporary_ids set
482
    @rtype: set
483
    @return: a set of IDs
484

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

    
495
  def _GenerateUniqueID(self, ec_id):
496
    """Generate an unique UUID.
497

498
    This checks the current node, instances and disk names for
499
    duplicates.
500

501
    @rtype: string
502
    @return: the unique id
503

504
    """
505
    existing = self._AllIDs(include_temporary=False)
506
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
507

    
508
  @locking.ssynchronized(_config_lock, shared=1)
509
  def GenerateUniqueID(self, ec_id):
510
    """Generate an unique ID.
511

512
    This is just a wrapper over the unlocked version.
513

514
    @type ec_id: string
515
    @param ec_id: unique id for the job to reserve the id to
516

517
    """
518
    return self._GenerateUniqueID(ec_id)
519

    
520
  def _AllMACs(self):
521
    """Return all MACs present in the config.
522

523
    @rtype: list
524
    @return: the list of all MACs
525

526
    """
527
    result = []
528
    for instance in self._config_data.instances.values():
529
      for nic in instance.nics:
530
        result.append(nic.mac)
531

    
532
    return result
533

    
534
  def _AllDRBDSecrets(self):
535
    """Return all DRBD secrets present in the config.
536

537
    @rtype: list
538
    @return: the list of all DRBD secrets
539

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

    
549
    result = []
550
    for instance in self._config_data.instances.values():
551
      for disk in instance.disks:
552
        helper(disk, result)
553

    
554
    return result
555

    
556
  def _CheckDiskIDs(self, disk, l_ids):
557
    """Compute duplicate disk IDs
558

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

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

    
574
    if disk.children:
575
      for child in disk.children:
576
        result.extend(self._CheckDiskIDs(child, l_ids))
577
    return result
578

    
579
  def _UnlockedVerifyConfig(self):
580
    """Verify function.
581

582
    @rtype: list
583
    @return: a list of error messages; a non-empty list signifies
584
        configuration errors
585

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

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

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

    
616
    if cluster.master_node not in data.nodes:
617
      result.append("cluster has invalid primary node '%s'" %
618
                    cluster.master_node)
619

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

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

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

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

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

    
670
    # per-instance checks
671
    for instance_uuid in data.instances:
672
      instance = data.instances[instance_uuid]
673
      if instance.uuid != instance_uuid:
674
        result.append("instance '%s' is indexed by wrong UUID '%s'" %
675
                      (instance.name, instance_uuid))
676
      if instance.primary_node not in data.nodes:
677
        result.append("instance '%s' has invalid primary node '%s'" %
678
                      (instance.name, instance.primary_node))
679
      for snode in instance.secondary_nodes:
680
        if snode not in data.nodes:
681
          result.append("instance '%s' has invalid secondary node '%s'" %
682
                        (instance.name, snode))
683
      for idx, nic in enumerate(instance.nics):
684
        if nic.mac in seen_macs:
685
          result.append("instance '%s' has NIC %d mac %s duplicate" %
686
                        (instance.name, idx, nic.mac))
687
        else:
688
          seen_macs.append(nic.mac)
689
        if nic.nicparams:
690
          filled = cluster.SimpleFillNIC(nic.nicparams)
691
          owner = "instance %s nic %d" % (instance.name, idx)
692
          _helper(owner, "nicparams",
693
                  filled, constants.NICS_PARAMETER_TYPES)
694
          _helper_nic(owner, filled)
695

    
696
      # disk template checks
697
      if not instance.disk_template in data.cluster.enabled_disk_templates:
698
        result.append("instance '%s' uses the disabled disk template '%s'." %
699
                      (instance.name, instance.disk_template))
700

    
701
      # parameter checks
702
      if instance.beparams:
703
        _helper("instance %s" % instance.name, "beparams",
704
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
705

    
706
      # gather the drbd ports for duplicate checks
707
      for (idx, dsk) in enumerate(instance.disks):
708
        if dsk.dev_type in constants.LDS_DRBD:
709
          tcp_port = dsk.logical_id[2]
710
          if tcp_port not in ports:
711
            ports[tcp_port] = []
712
          ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
713
      # gather network port reservation
714
      net_port = getattr(instance, "network_port", None)
715
      if net_port is not None:
716
        if net_port not in ports:
717
          ports[net_port] = []
718
        ports[net_port].append((instance.name, "network port"))
719

    
720
      # instance disk verify
721
      for idx, disk in enumerate(instance.disks):
722
        result.extend(["instance '%s' disk %d error: %s" %
723
                       (instance.name, idx, msg) for msg in disk.Verify()])
724
        result.extend(self._CheckDiskIDs(disk, seen_lids))
725

    
726
      wrong_names = _CheckInstanceDiskIvNames(instance.disks)
727
      if wrong_names:
728
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
729
                         (idx, exp_name, actual_name))
730
                        for (idx, exp_name, actual_name) in wrong_names)
731

    
732
        result.append("Instance '%s' has wrongly named disks: %s" %
733
                      (instance.name, tmp))
734

    
735
    # cluster-wide pool of free ports
736
    for free_port in cluster.tcpudp_port_pool:
737
      if free_port not in ports:
738
        ports[free_port] = []
739
      ports[free_port].append(("cluster", "port marked as free"))
740

    
741
    # compute tcp/udp duplicate ports
742
    keys = ports.keys()
743
    keys.sort()
744
    for pnum in keys:
745
      pdata = ports[pnum]
746
      if len(pdata) > 1:
747
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
748
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
749

    
750
    # highest used tcp port check
751
    if keys:
752
      if keys[-1] > cluster.highest_used_port:
753
        result.append("Highest used port mismatch, saved %s, computed %s" %
754
                      (cluster.highest_used_port, keys[-1]))
755

    
756
    if not data.nodes[cluster.master_node].master_candidate:
757
      result.append("Master node is not a master candidate")
758

    
759
    # master candidate checks
760
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
761
    if mc_now < mc_max:
762
      result.append("Not enough master candidates: actual %d, target %d" %
763
                    (mc_now, mc_max))
764

    
765
    # node checks
766
    for node_uuid, node in data.nodes.items():
767
      if node.uuid != node_uuid:
768
        result.append("Node '%s' is indexed by wrong UUID '%s'" %
769
                      (node.name, node_uuid))
770
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
771
        result.append("Node %s state is invalid: master_candidate=%s,"
772
                      " drain=%s, offline=%s" %
773
                      (node.name, node.master_candidate, node.drained,
774
                       node.offline))
775
      if node.group not in data.nodegroups:
776
        result.append("Node '%s' has invalid group '%s'" %
777
                      (node.name, node.group))
778
      else:
779
        _helper("node %s" % node.name, "ndparams",
780
                cluster.FillND(node, data.nodegroups[node.group]),
781
                constants.NDS_PARAMETER_TYPES)
782
      used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
783
      if used_globals:
784
        result.append("Node '%s' has some global parameters set: %s" %
785
                      (node.name, utils.CommaJoin(used_globals)))
786

    
787
    # nodegroups checks
788
    nodegroups_names = set()
789
    for nodegroup_uuid in data.nodegroups:
790
      nodegroup = data.nodegroups[nodegroup_uuid]
791
      if nodegroup.uuid != nodegroup_uuid:
792
        result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
793
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
794
      if utils.UUID_RE.match(nodegroup.name.lower()):
795
        result.append("node group '%s' (uuid: '%s') has uuid-like name" %
796
                      (nodegroup.name, nodegroup.uuid))
797
      if nodegroup.name in nodegroups_names:
798
        result.append("duplicate node group name '%s'" % nodegroup.name)
799
      else:
800
        nodegroups_names.add(nodegroup.name)
801
      group_name = "group %s" % nodegroup.name
802
      _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
803
                      False)
804
      if nodegroup.ndparams:
805
        _helper(group_name, "ndparams",
806
                cluster.SimpleFillND(nodegroup.ndparams),
807
                constants.NDS_PARAMETER_TYPES)
808

    
809
    # drbd minors check
810
    _, duplicates = self._UnlockedComputeDRBDMap()
811
    for node, minor, instance_a, instance_b in duplicates:
812
      result.append("DRBD minor %d on node %s is assigned twice to instances"
813
                    " %s and %s" % (minor, node, instance_a, instance_b))
814

    
815
    # IP checks
816
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
817
    ips = {}
818

    
819
    def _AddIpAddress(ip, name):
820
      ips.setdefault(ip, []).append(name)
821

    
822
    _AddIpAddress(cluster.master_ip, "cluster_ip")
823

    
824
    for node in data.nodes.values():
825
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
826
      if node.secondary_ip != node.primary_ip:
827
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
828

    
829
    for instance in data.instances.values():
830
      for idx, nic in enumerate(instance.nics):
831
        if nic.ip is None:
832
          continue
833

    
834
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
835
        nic_mode = nicparams[constants.NIC_MODE]
836
        nic_link = nicparams[constants.NIC_LINK]
837

    
838
        if nic_mode == constants.NIC_MODE_BRIDGED:
839
          link = "bridge:%s" % nic_link
840
        elif nic_mode == constants.NIC_MODE_ROUTED:
841
          link = "route:%s" % nic_link
842
        else:
843
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
844

    
845
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
846
                      "instance:%s/nic:%d" % (instance.name, idx))
847

    
848
    for ip, owners in ips.items():
849
      if len(owners) > 1:
850
        result.append("IP address %s is used by multiple owners: %s" %
851
                      (ip, utils.CommaJoin(owners)))
852

    
853
    return result
854

    
855
  @locking.ssynchronized(_config_lock, shared=1)
856
  def VerifyConfig(self):
857
    """Verify function.
858

859
    This is just a wrapper over L{_UnlockedVerifyConfig}.
860

861
    @rtype: list
862
    @return: a list of error messages; a non-empty list signifies
863
        configuration errors
864

865
    """
866
    return self._UnlockedVerifyConfig()
867

    
868
  @locking.ssynchronized(_config_lock)
869
  def AddTcpUdpPort(self, port):
870
    """Adds a new port to the available port pool.
871

872
    @warning: this method does not "flush" the configuration (via
873
        L{_WriteConfig}); callers should do that themselves once the
874
        configuration is stable
875

876
    """
877
    if not isinstance(port, int):
878
      raise errors.ProgrammerError("Invalid type passed for port")
879

    
880
    self._config_data.cluster.tcpudp_port_pool.add(port)
881

    
882
  @locking.ssynchronized(_config_lock, shared=1)
883
  def GetPortList(self):
884
    """Returns a copy of the current port list.
885

886
    """
887
    return self._config_data.cluster.tcpudp_port_pool.copy()
888

    
889
  @locking.ssynchronized(_config_lock)
890
  def AllocatePort(self):
891
    """Allocate a port.
892

893
    The port will be taken from the available port pool or from the
894
    default port range (and in this case we increase
895
    highest_used_port).
896

897
    """
898
    # If there are TCP/IP ports configured, we use them first.
899
    if self._config_data.cluster.tcpudp_port_pool:
900
      port = self._config_data.cluster.tcpudp_port_pool.pop()
901
    else:
902
      port = self._config_data.cluster.highest_used_port + 1
903
      if port >= constants.LAST_DRBD_PORT:
904
        raise errors.ConfigurationError("The highest used port is greater"
905
                                        " than %s. Aborting." %
906
                                        constants.LAST_DRBD_PORT)
907
      self._config_data.cluster.highest_used_port = port
908

    
909
    self._WriteConfig()
910
    return port
911

    
912
  def _UnlockedComputeDRBDMap(self):
913
    """Compute the used DRBD minor/nodes.
914

915
    @rtype: (dict, list)
916
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
917
        the returned dict will have all the nodes in it (even if with
918
        an empty list), and a list of duplicates; if the duplicates
919
        list is not empty, the configuration is corrupted and its caller
920
        should raise an exception
921

922
    """
923
    def _AppendUsedMinors(get_node_name_fn, instance, disk, used):
924
      duplicates = []
925
      if disk.dev_type == constants.DT_DRBD8 and len(disk.logical_id) >= 5:
926
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
927
        for node_uuid, minor in ((node_a, minor_a), (node_b, minor_b)):
928
          assert node_uuid in used, \
929
            ("Node '%s' of instance '%s' not found in node list" %
930
             (get_node_name_fn(node_uuid), instance.name))
931
          if minor in used[node_uuid]:
932
            duplicates.append((node_uuid, minor, instance.uuid,
933
                               used[node_uuid][minor]))
934
          else:
935
            used[node_uuid][minor] = instance.uuid
936
      if disk.children:
937
        for child in disk.children:
938
          duplicates.extend(_AppendUsedMinors(get_node_name_fn, instance, child,
939
                                              used))
940
      return duplicates
941

    
942
    duplicates = []
943
    my_dict = dict((node_uuid, {}) for node_uuid in self._config_data.nodes)
944
    for instance in self._config_data.instances.itervalues():
945
      for disk in instance.disks:
946
        duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
947
                                            instance, disk, my_dict))
948
    for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
949
      if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
950
        duplicates.append((node_uuid, minor, inst_uuid,
951
                           my_dict[node_uuid][minor]))
952
      else:
953
        my_dict[node_uuid][minor] = inst_uuid
954
    return my_dict, duplicates
955

    
956
  @locking.ssynchronized(_config_lock)
957
  def ComputeDRBDMap(self):
958
    """Compute the used DRBD minor/nodes.
959

960
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
961

962
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
963
        the returned dict will have all the nodes in it (even if with
964
        an empty list).
965

966
    """
967
    d_map, duplicates = self._UnlockedComputeDRBDMap()
968
    if duplicates:
969
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
970
                                      str(duplicates))
971
    return d_map
972

    
973
  @locking.ssynchronized(_config_lock)
974
  def AllocateDRBDMinor(self, node_uuids, inst_uuid):
975
    """Allocate a drbd minor.
976

977
    The free minor will be automatically computed from the existing
978
    devices. A node can be given multiple times in order to allocate
979
    multiple minors. The result is the list of minors, in the same
980
    order as the passed nodes.
981

982
    @type inst_uuid: string
983
    @param inst_uuid: the instance for which we allocate minors
984

985
    """
986
    assert isinstance(inst_uuid, basestring), \
987
           "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
988

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

    
1029
  def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1030
    """Release temporary drbd minors allocated for a given instance.
1031

1032
    @type inst_uuid: string
1033
    @param inst_uuid: the instance for which temporary minors should be
1034
                      released
1035

1036
    """
1037
    assert isinstance(inst_uuid, basestring), \
1038
           "Invalid argument passed to ReleaseDRBDMinors"
1039
    for key, uuid in self._temporary_drbds.items():
1040
      if uuid == inst_uuid:
1041
        del self._temporary_drbds[key]
1042

    
1043
  @locking.ssynchronized(_config_lock)
1044
  def ReleaseDRBDMinors(self, inst_uuid):
1045
    """Release temporary drbd minors allocated for a given instance.
1046

1047
    This should be called on the error paths, on the success paths
1048
    it's automatically called by the ConfigWriter add and update
1049
    functions.
1050

1051
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1052

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

1057
    """
1058
    self._UnlockedReleaseDRBDMinors(inst_uuid)
1059

    
1060
  @locking.ssynchronized(_config_lock, shared=1)
1061
  def GetConfigVersion(self):
1062
    """Get the configuration version.
1063

1064
    @return: Config version
1065

1066
    """
1067
    return self._config_data.version
1068

    
1069
  @locking.ssynchronized(_config_lock, shared=1)
1070
  def GetClusterName(self):
1071
    """Get cluster name.
1072

1073
    @return: Cluster name
1074

1075
    """
1076
    return self._config_data.cluster.cluster_name
1077

    
1078
  @locking.ssynchronized(_config_lock, shared=1)
1079
  def GetMasterNode(self):
1080
    """Get the UUID of the master node for this cluster.
1081

1082
    @return: Master node UUID
1083

1084
    """
1085
    return self._config_data.cluster.master_node
1086

    
1087
  @locking.ssynchronized(_config_lock, shared=1)
1088
  def GetMasterNodeName(self):
1089
    """Get the hostname of the master node for this cluster.
1090

1091
    @return: Master node hostname
1092

1093
    """
1094
    return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
1095

    
1096
  @locking.ssynchronized(_config_lock, shared=1)
1097
  def GetMasterNodeInfo(self):
1098
    """Get the master node information for this cluster.
1099

1100
    @rtype: objects.Node
1101
    @return: Master node L{objects.Node} object
1102

1103
    """
1104
    return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
1105

    
1106
  @locking.ssynchronized(_config_lock, shared=1)
1107
  def GetMasterIP(self):
1108
    """Get the IP of the master node for this cluster.
1109

1110
    @return: Master IP
1111

1112
    """
1113
    return self._config_data.cluster.master_ip
1114

    
1115
  @locking.ssynchronized(_config_lock, shared=1)
1116
  def GetMasterNetdev(self):
1117
    """Get the master network device for this cluster.
1118

1119
    """
1120
    return self._config_data.cluster.master_netdev
1121

    
1122
  @locking.ssynchronized(_config_lock, shared=1)
1123
  def GetMasterNetmask(self):
1124
    """Get the netmask of the master node for this cluster.
1125

1126
    """
1127
    return self._config_data.cluster.master_netmask
1128

    
1129
  @locking.ssynchronized(_config_lock, shared=1)
1130
  def GetUseExternalMipScript(self):
1131
    """Get flag representing whether to use the external master IP setup script.
1132

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

    
1136
  @locking.ssynchronized(_config_lock, shared=1)
1137
  def GetFileStorageDir(self):
1138
    """Get the file storage dir for this cluster.
1139

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

    
1143
  @locking.ssynchronized(_config_lock, shared=1)
1144
  def GetSharedFileStorageDir(self):
1145
    """Get the shared file storage dir for this cluster.
1146

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

    
1150
  @locking.ssynchronized(_config_lock, shared=1)
1151
  def GetHypervisorType(self):
1152
    """Get the hypervisor type for this cluster.
1153

1154
    """
1155
    return self._config_data.cluster.enabled_hypervisors[0]
1156

    
1157
  @locking.ssynchronized(_config_lock, shared=1)
1158
  def GetRsaHostKey(self):
1159
    """Return the rsa hostkey from the config.
1160

1161
    @rtype: string
1162
    @return: the rsa hostkey
1163

1164
    """
1165
    return self._config_data.cluster.rsahostkeypub
1166

    
1167
  @locking.ssynchronized(_config_lock, shared=1)
1168
  def GetDsaHostKey(self):
1169
    """Return the dsa hostkey from the config.
1170

1171
    @rtype: string
1172
    @return: the dsa hostkey
1173

1174
    """
1175
    return self._config_data.cluster.dsahostkeypub
1176

    
1177
  @locking.ssynchronized(_config_lock, shared=1)
1178
  def GetDefaultIAllocator(self):
1179
    """Get the default instance allocator for this cluster.
1180

1181
    """
1182
    return self._config_data.cluster.default_iallocator
1183

    
1184
  @locking.ssynchronized(_config_lock, shared=1)
1185
  def GetPrimaryIPFamily(self):
1186
    """Get cluster primary ip family.
1187

1188
    @return: primary ip family
1189

1190
    """
1191
    return self._config_data.cluster.primary_ip_family
1192

    
1193
  @locking.ssynchronized(_config_lock, shared=1)
1194
  def GetMasterNetworkParameters(self):
1195
    """Get network parameters of the master node.
1196

1197
    @rtype: L{object.MasterNetworkParameters}
1198
    @return: network parameters of the master node
1199

1200
    """
1201
    cluster = self._config_data.cluster
1202
    result = objects.MasterNetworkParameters(
1203
      uuid=cluster.master_node, ip=cluster.master_ip,
1204
      netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1205
      ip_family=cluster.primary_ip_family)
1206

    
1207
    return result
1208

    
1209
  @locking.ssynchronized(_config_lock)
1210
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1211
    """Add a node group to the configuration.
1212

1213
    This method calls group.UpgradeConfig() to fill any missing attributes
1214
    according to their default values.
1215

1216
    @type group: L{objects.NodeGroup}
1217
    @param group: the NodeGroup object to add
1218
    @type ec_id: string
1219
    @param ec_id: unique id for the job to use when creating a missing UUID
1220
    @type check_uuid: bool
1221
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1222
                       it does, ensure that it does not exist in the
1223
                       configuration already
1224

1225
    """
1226
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1227
    self._WriteConfig()
1228

    
1229
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1230
    """Add a node group to the configuration.
1231

1232
    """
1233
    logging.info("Adding node group %s to configuration", group.name)
1234

    
1235
    # Some code might need to add a node group with a pre-populated UUID
1236
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1237
    # the "does this UUID" exist already check.
1238
    if check_uuid:
1239
      self._EnsureUUID(group, ec_id)
1240

    
1241
    try:
1242
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1243
    except errors.OpPrereqError:
1244
      pass
1245
    else:
1246
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1247
                                 " node group (UUID: %s)" %
1248
                                 (group.name, existing_uuid),
1249
                                 errors.ECODE_EXISTS)
1250

    
1251
    group.serial_no = 1
1252
    group.ctime = group.mtime = time.time()
1253
    group.UpgradeConfig()
1254

    
1255
    self._config_data.nodegroups[group.uuid] = group
1256
    self._config_data.cluster.serial_no += 1
1257

    
1258
  @locking.ssynchronized(_config_lock)
1259
  def RemoveNodeGroup(self, group_uuid):
1260
    """Remove a node group from the configuration.
1261

1262
    @type group_uuid: string
1263
    @param group_uuid: the UUID of the node group to remove
1264

1265
    """
1266
    logging.info("Removing node group %s from configuration", group_uuid)
1267

    
1268
    if group_uuid not in self._config_data.nodegroups:
1269
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1270

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

    
1274
    del self._config_data.nodegroups[group_uuid]
1275
    self._config_data.cluster.serial_no += 1
1276
    self._WriteConfig()
1277

    
1278
  def _UnlockedLookupNodeGroup(self, target):
1279
    """Lookup a node group's UUID.
1280

1281
    @type target: string or None
1282
    @param target: group name or UUID or None to look for the default
1283
    @rtype: string
1284
    @return: nodegroup UUID
1285
    @raises errors.OpPrereqError: when the target group cannot be found
1286

1287
    """
1288
    if target is None:
1289
      if len(self._config_data.nodegroups) != 1:
1290
        raise errors.OpPrereqError("More than one node group exists. Target"
1291
                                   " group must be specified explicitly.")
1292
      else:
1293
        return self._config_data.nodegroups.keys()[0]
1294
    if target in self._config_data.nodegroups:
1295
      return target
1296
    for nodegroup in self._config_data.nodegroups.values():
1297
      if nodegroup.name == target:
1298
        return nodegroup.uuid
1299
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1300
                               errors.ECODE_NOENT)
1301

    
1302
  @locking.ssynchronized(_config_lock, shared=1)
1303
  def LookupNodeGroup(self, target):
1304
    """Lookup a node group's UUID.
1305

1306
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1307

1308
    @type target: string or None
1309
    @param target: group name or UUID or None to look for the default
1310
    @rtype: string
1311
    @return: nodegroup UUID
1312

1313
    """
1314
    return self._UnlockedLookupNodeGroup(target)
1315

    
1316
  def _UnlockedGetNodeGroup(self, uuid):
1317
    """Lookup a node group.
1318

1319
    @type uuid: string
1320
    @param uuid: group UUID
1321
    @rtype: L{objects.NodeGroup} or None
1322
    @return: nodegroup object, or None if not found
1323

1324
    """
1325
    if uuid not in self._config_data.nodegroups:
1326
      return None
1327

    
1328
    return self._config_data.nodegroups[uuid]
1329

    
1330
  @locking.ssynchronized(_config_lock, shared=1)
1331
  def GetNodeGroup(self, uuid):
1332
    """Lookup a node group.
1333

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

1339
    """
1340
    return self._UnlockedGetNodeGroup(uuid)
1341

    
1342
  @locking.ssynchronized(_config_lock, shared=1)
1343
  def GetAllNodeGroupsInfo(self):
1344
    """Get the configuration of all node groups.
1345

1346
    """
1347
    return dict(self._config_data.nodegroups)
1348

    
1349
  @locking.ssynchronized(_config_lock, shared=1)
1350
  def GetNodeGroupList(self):
1351
    """Get a list of node groups.
1352

1353
    """
1354
    return self._config_data.nodegroups.keys()
1355

    
1356
  @locking.ssynchronized(_config_lock, shared=1)
1357
  def GetNodeGroupMembersByNodes(self, nodes):
1358
    """Get nodes which are member in the same nodegroups as the given nodes.
1359

1360
    """
1361
    ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1362
    return frozenset(member_uuid
1363
                     for node_uuid in nodes
1364
                     for member_uuid in
1365
                       self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1366

    
1367
  @locking.ssynchronized(_config_lock, shared=1)
1368
  def GetMultiNodeGroupInfo(self, group_uuids):
1369
    """Get the configuration of multiple node groups.
1370

1371
    @param group_uuids: List of node group UUIDs
1372
    @rtype: list
1373
    @return: List of tuples of (group_uuid, group_info)
1374

1375
    """
1376
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1377

    
1378
  @locking.ssynchronized(_config_lock)
1379
  def AddInstance(self, instance, ec_id):
1380
    """Add an instance to the config.
1381

1382
    This should be used after creating a new instance.
1383

1384
    @type instance: L{objects.Instance}
1385
    @param instance: the instance object
1386

1387
    """
1388
    if not isinstance(instance, objects.Instance):
1389
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1390

    
1391
    if instance.disk_template != constants.DT_DISKLESS:
1392
      all_lvs = instance.MapLVsByNode()
1393
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1394

    
1395
    all_macs = self._AllMACs()
1396
    for nic in instance.nics:
1397
      if nic.mac in all_macs:
1398
        raise errors.ConfigurationError("Cannot add instance %s:"
1399
                                        " MAC address '%s' already in use." %
1400
                                        (instance.name, nic.mac))
1401

    
1402
    self._CheckUniqueUUID(instance, include_temporary=False)
1403

    
1404
    instance.serial_no = 1
1405
    instance.ctime = instance.mtime = time.time()
1406
    self._config_data.instances[instance.uuid] = instance
1407
    self._config_data.cluster.serial_no += 1
1408
    self._UnlockedReleaseDRBDMinors(instance.uuid)
1409
    self._UnlockedCommitTemporaryIps(ec_id)
1410
    self._WriteConfig()
1411

    
1412
  def _EnsureUUID(self, item, ec_id):
1413
    """Ensures a given object has a valid UUID.
1414

1415
    @param item: the instance or node to be checked
1416
    @param ec_id: the execution context id for the uuid reservation
1417

1418
    """
1419
    if not item.uuid:
1420
      item.uuid = self._GenerateUniqueID(ec_id)
1421
    else:
1422
      self._CheckUniqueUUID(item, include_temporary=True)
1423

    
1424
  def _CheckUniqueUUID(self, item, include_temporary):
1425
    """Checks that the UUID of the given object is unique.
1426

1427
    @param item: the instance or node to be checked
1428
    @param include_temporary: whether temporarily generated UUID's should be
1429
              included in the check. If the UUID of the item to be checked is
1430
              a temporarily generated one, this has to be C{False}.
1431

1432
    """
1433
    if not item.uuid:
1434
      raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1435
    if item.uuid in self._AllIDs(include_temporary=include_temporary):
1436
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1437
                                      " in use" % (item.name, item.uuid))
1438

    
1439
  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1440
    """Set the instance's status to a given value.
1441

1442
    """
1443
    if inst_uuid not in self._config_data.instances:
1444
      raise errors.ConfigurationError("Unknown instance '%s'" %
1445
                                      inst_uuid)
1446
    instance = self._config_data.instances[inst_uuid]
1447

    
1448
    if status is None:
1449
      status = instance.admin_state
1450
    if disks_active is None:
1451
      disks_active = instance.disks_active
1452

    
1453
    assert status in constants.ADMINST_ALL, \
1454
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1455

    
1456
    if instance.admin_state != status or \
1457
       instance.disks_active != disks_active:
1458
      instance.admin_state = status
1459
      instance.disks_active = disks_active
1460
      instance.serial_no += 1
1461
      instance.mtime = time.time()
1462
      self._WriteConfig()
1463

    
1464
  @locking.ssynchronized(_config_lock)
1465
  def MarkInstanceUp(self, inst_uuid):
1466
    """Mark the instance status to up in the config.
1467

1468
    This also sets the instance disks active flag.
1469

1470
    """
1471
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1472

    
1473
  @locking.ssynchronized(_config_lock)
1474
  def MarkInstanceOffline(self, inst_uuid):
1475
    """Mark the instance status to down in the config.
1476

1477
    This also clears the instance disks active flag.
1478

1479
    """
1480
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1481

    
1482
  @locking.ssynchronized(_config_lock)
1483
  def RemoveInstance(self, inst_uuid):
1484
    """Remove the instance from the configuration.
1485

1486
    """
1487
    if inst_uuid not in self._config_data.instances:
1488
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1489

    
1490
    # If a network port has been allocated to the instance,
1491
    # return it to the pool of free ports.
1492
    inst = self._config_data.instances[inst_uuid]
1493
    network_port = getattr(inst, "network_port", None)
1494
    if network_port is not None:
1495
      self._config_data.cluster.tcpudp_port_pool.add(network_port)
1496

    
1497
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1498

    
1499
    for nic in instance.nics:
1500
      if nic.network and nic.ip:
1501
        # Return all IP addresses to the respective address pools
1502
        self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1503

    
1504
    del self._config_data.instances[inst_uuid]
1505
    self._config_data.cluster.serial_no += 1
1506
    self._WriteConfig()
1507

    
1508
  @locking.ssynchronized(_config_lock)
1509
  def RenameInstance(self, inst_uuid, new_name):
1510
    """Rename an instance.
1511

1512
    This needs to be done in ConfigWriter and not by RemoveInstance
1513
    combined with AddInstance as only we can guarantee an atomic
1514
    rename.
1515

1516
    """
1517
    if inst_uuid not in self._config_data.instances:
1518
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1519

    
1520
    inst = self._config_data.instances[inst_uuid]
1521
    inst.name = new_name
1522

    
1523
    for (idx, disk) in enumerate(inst.disks):
1524
      if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1525
        # rename the file paths in logical and physical id
1526
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1527
        disk.logical_id = (disk.logical_id[0],
1528
                           utils.PathJoin(file_storage_dir, inst.name,
1529
                                          "disk%s" % idx))
1530

    
1531
    # Force update of ssconf files
1532
    self._config_data.cluster.serial_no += 1
1533

    
1534
    self._WriteConfig()
1535

    
1536
  @locking.ssynchronized(_config_lock)
1537
  def MarkInstanceDown(self, inst_uuid):
1538
    """Mark the status of an instance to down in the configuration.
1539

1540
    This does not touch the instance disks active flag, as shut down instances
1541
    can still have active disks.
1542

1543
    """
1544
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1545

    
1546
  @locking.ssynchronized(_config_lock)
1547
  def MarkInstanceDisksActive(self, inst_uuid):
1548
    """Mark the status of instance disks active.
1549

1550
    """
1551
    self._SetInstanceStatus(inst_uuid, None, True)
1552

    
1553
  @locking.ssynchronized(_config_lock)
1554
  def MarkInstanceDisksInactive(self, inst_uuid):
1555
    """Mark the status of instance disks inactive.
1556

1557
    """
1558
    self._SetInstanceStatus(inst_uuid, None, False)
1559

    
1560
  def _UnlockedGetInstanceList(self):
1561
    """Get the list of instances.
1562

1563
    This function is for internal use, when the config lock is already held.
1564

1565
    """
1566
    return self._config_data.instances.keys()
1567

    
1568
  @locking.ssynchronized(_config_lock, shared=1)
1569
  def GetInstanceList(self):
1570
    """Get the list of instances.
1571

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

1574
    """
1575
    return self._UnlockedGetInstanceList()
1576

    
1577
  def ExpandInstanceName(self, short_name):
1578
    """Attempt to expand an incomplete instance name.
1579

1580
    """
1581
    # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1582
    all_insts = self.GetAllInstancesInfo().values()
1583
    expanded_name = _MatchNameComponentIgnoreCase(
1584
                      short_name, [inst.name for inst in all_insts])
1585

    
1586
    if expanded_name is not None:
1587
      # there has to be exactly one instance with that name
1588
      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1589
      return (inst.uuid, inst.name)
1590
    else:
1591
      return (None, None)
1592

    
1593
  def _UnlockedGetInstanceInfo(self, inst_uuid):
1594
    """Returns information about an instance.
1595

1596
    This function is for internal use, when the config lock is already held.
1597

1598
    """
1599
    if inst_uuid not in self._config_data.instances:
1600
      return None
1601

    
1602
    return self._config_data.instances[inst_uuid]
1603

    
1604
  @locking.ssynchronized(_config_lock, shared=1)
1605
  def GetInstanceInfo(self, inst_uuid):
1606
    """Returns information about an instance.
1607

1608
    It takes the information from the configuration file. Other information of
1609
    an instance are taken from the live systems.
1610

1611
    @param inst_uuid: UUID of the instance
1612

1613
    @rtype: L{objects.Instance}
1614
    @return: the instance object
1615

1616
    """
1617
    return self._UnlockedGetInstanceInfo(inst_uuid)
1618

    
1619
  @locking.ssynchronized(_config_lock, shared=1)
1620
  def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1621
    """Returns set of node group UUIDs for instance's nodes.
1622

1623
    @rtype: frozenset
1624

1625
    """
1626
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1627
    if not instance:
1628
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1629

    
1630
    if primary_only:
1631
      nodes = [instance.primary_node]
1632
    else:
1633
      nodes = instance.all_nodes
1634

    
1635
    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1636
                     for node_uuid in nodes)
1637

    
1638
  @locking.ssynchronized(_config_lock, shared=1)
1639
  def GetInstanceNetworks(self, inst_uuid):
1640
    """Returns set of network UUIDs for instance's nics.
1641

1642
    @rtype: frozenset
1643

1644
    """
1645
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1646
    if not instance:
1647
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1648

    
1649
    networks = set()
1650
    for nic in instance.nics:
1651
      if nic.network:
1652
        networks.add(nic.network)
1653

    
1654
    return frozenset(networks)
1655

    
1656
  @locking.ssynchronized(_config_lock, shared=1)
1657
  def GetMultiInstanceInfo(self, inst_uuids):
1658
    """Get the configuration of multiple instances.
1659

1660
    @param inst_uuids: list of instance UUIDs
1661
    @rtype: list
1662
    @return: list of tuples (instance UUID, instance_info), where
1663
        instance_info is what would GetInstanceInfo return for the
1664
        node, while keeping the original order
1665

1666
    """
1667
    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1668

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

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

1679
    """
1680
    result = []
1681
    for name in inst_names:
1682
      instance = self._UnlockedGetInstanceInfoByName(name)
1683
      result.append((instance.uuid, instance))
1684
    return result
1685

    
1686
  @locking.ssynchronized(_config_lock, shared=1)
1687
  def GetAllInstancesInfo(self):
1688
    """Get the configuration of all instances.
1689

1690
    @rtype: dict
1691
    @return: dict of (instance, instance_info), where instance_info is what
1692
              would GetInstanceInfo return for the node
1693

1694
    """
1695
    return self._UnlockedGetAllInstancesInfo()
1696

    
1697
  def _UnlockedGetAllInstancesInfo(self):
1698
    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1699
                    for inst_uuid in self._UnlockedGetInstanceList()])
1700
    return my_dict
1701

    
1702
  @locking.ssynchronized(_config_lock, shared=1)
1703
  def GetInstancesInfoByFilter(self, filter_fn):
1704
    """Get instance configuration with a filter.
1705

1706
    @type filter_fn: callable
1707
    @param filter_fn: Filter function receiving instance object as parameter,
1708
      returning boolean. Important: this function is called while the
1709
      configuration locks is held. It must not do any complex work or call
1710
      functions potentially leading to a deadlock. Ideally it doesn't call any
1711
      other functions and just compares instance attributes.
1712

1713
    """
1714
    return dict((uuid, inst)
1715
                for (uuid, inst) in self._config_data.instances.items()
1716
                if filter_fn(inst))
1717

    
1718
  @locking.ssynchronized(_config_lock, shared=1)
1719
  def GetInstanceInfoByName(self, inst_name):
1720
    """Get the L{objects.Instance} object for a named instance.
1721

1722
    @param inst_name: name of the instance to get information for
1723
    @type inst_name: string
1724
    @return: the corresponding L{objects.Instance} instance or None if no
1725
          information is available
1726

1727
    """
1728
    return self._UnlockedGetInstanceInfoByName(inst_name)
1729

    
1730
  def _UnlockedGetInstanceInfoByName(self, inst_name):
1731
    for inst in self._UnlockedGetAllInstancesInfo().values():
1732
      if inst.name == inst_name:
1733
        return inst
1734
    return None
1735

    
1736
  def _UnlockedGetInstanceName(self, inst_uuid):
1737
    inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1738
    if inst_info is None:
1739
      raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1740
    return inst_info.name
1741

    
1742
  @locking.ssynchronized(_config_lock, shared=1)
1743
  def GetInstanceName(self, inst_uuid):
1744
    """Gets the instance name for the passed instance.
1745

1746
    @param inst_uuid: instance UUID to get name for
1747
    @type inst_uuid: string
1748
    @rtype: string
1749
    @return: instance name
1750

1751
    """
1752
    return self._UnlockedGetInstanceName(inst_uuid)
1753

    
1754
  @locking.ssynchronized(_config_lock, shared=1)
1755
  def GetInstanceNames(self, inst_uuids):
1756
    """Gets the instance names for the passed list of nodes.
1757

1758
    @param inst_uuids: list of instance UUIDs to get names for
1759
    @type inst_uuids: list of strings
1760
    @rtype: list of strings
1761
    @return: list of instance names
1762

1763
    """
1764
    return self._UnlockedGetInstanceNames(inst_uuids)
1765

    
1766
  def _UnlockedGetInstanceNames(self, inst_uuids):
1767
    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1768

    
1769
  @locking.ssynchronized(_config_lock)
1770
  def AddNode(self, node, ec_id):
1771
    """Add a node to the configuration.
1772

1773
    @type node: L{objects.Node}
1774
    @param node: a Node instance
1775

1776
    """
1777
    logging.info("Adding node %s to configuration", node.name)
1778

    
1779
    self._EnsureUUID(node, ec_id)
1780

    
1781
    node.serial_no = 1
1782
    node.ctime = node.mtime = time.time()
1783
    self._UnlockedAddNodeToGroup(node.uuid, node.group)
1784
    self._config_data.nodes[node.uuid] = node
1785
    self._config_data.cluster.serial_no += 1
1786
    self._WriteConfig()
1787

    
1788
  @locking.ssynchronized(_config_lock)
1789
  def RemoveNode(self, node_uuid):
1790
    """Remove a node from the configuration.
1791

1792
    """
1793
    logging.info("Removing node %s from configuration", node_uuid)
1794

    
1795
    if node_uuid not in self._config_data.nodes:
1796
      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1797

    
1798
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1799
    del self._config_data.nodes[node_uuid]
1800
    self._config_data.cluster.serial_no += 1
1801
    self._WriteConfig()
1802

    
1803
  def ExpandNodeName(self, short_name):
1804
    """Attempt to expand an incomplete node name into a node UUID.
1805

1806
    """
1807
    # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1808
    all_nodes = self.GetAllNodesInfo().values()
1809
    expanded_name = _MatchNameComponentIgnoreCase(
1810
                      short_name, [node.name for node in all_nodes])
1811

    
1812
    if expanded_name is not None:
1813
      # there has to be exactly one node with that name
1814
      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1815
      return (node.uuid, node.name)
1816
    else:
1817
      return (None, None)
1818

    
1819
  def _UnlockedGetNodeInfo(self, node_uuid):
1820
    """Get the configuration of a node, as stored in the config.
1821

1822
    This function is for internal use, when the config lock is already
1823
    held.
1824

1825
    @param node_uuid: the node UUID
1826

1827
    @rtype: L{objects.Node}
1828
    @return: the node object
1829

1830
    """
1831
    if node_uuid not in self._config_data.nodes:
1832
      return None
1833

    
1834
    return self._config_data.nodes[node_uuid]
1835

    
1836
  @locking.ssynchronized(_config_lock, shared=1)
1837
  def GetNodeInfo(self, node_uuid):
1838
    """Get the configuration of a node, as stored in the config.
1839

1840
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1841

1842
    @param node_uuid: the node UUID
1843

1844
    @rtype: L{objects.Node}
1845
    @return: the node object
1846

1847
    """
1848
    return self._UnlockedGetNodeInfo(node_uuid)
1849

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

1854
    @param node_uuid: the node UUID
1855

1856
    @rtype: (list, list)
1857
    @return: a tuple with two lists: the primary and the secondary instances
1858

1859
    """
1860
    pri = []
1861
    sec = []
1862
    for inst in self._config_data.instances.values():
1863
      if inst.primary_node == node_uuid:
1864
        pri.append(inst.uuid)
1865
      if node_uuid in inst.secondary_nodes:
1866
        sec.append(inst.uuid)
1867
    return (pri, sec)
1868

    
1869
  @locking.ssynchronized(_config_lock, shared=1)
1870
  def GetNodeGroupInstances(self, uuid, primary_only=False):
1871
    """Get the instances of a node group.
1872

1873
    @param uuid: Node group UUID
1874
    @param primary_only: Whether to only consider primary nodes
1875
    @rtype: frozenset
1876
    @return: List of instance UUIDs in node group
1877

1878
    """
1879
    if primary_only:
1880
      nodes_fn = lambda inst: [inst.primary_node]
1881
    else:
1882
      nodes_fn = lambda inst: inst.all_nodes
1883

    
1884
    return frozenset(inst.uuid
1885
                     for inst in self._config_data.instances.values()
1886
                     for node_uuid in nodes_fn(inst)
1887
                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1888

    
1889
  def _UnlockedGetHvparamsString(self, hvname):
1890
    """Return the string representation of the list of hyervisor parameters of
1891
    the given hypervisor.
1892

1893
    @see: C{GetHvparams}
1894

1895
    """
1896
    result = ""
1897
    hvparams = self._config_data.cluster.hvparams[hvname]
1898
    for key in hvparams:
1899
      result += "%s=%s\n" % (key, hvparams[key])
1900
    return result
1901

    
1902
  @locking.ssynchronized(_config_lock, shared=1)
1903
  def GetHvparamsString(self, hvname):
1904
    """Return the hypervisor parameters of the given hypervisor.
1905

1906
    @type hvname: string
1907
    @param hvname: name of a hypervisor
1908
    @rtype: string
1909
    @return: string containing key-value-pairs, one pair on each line;
1910
      format: KEY=VALUE
1911

1912
    """
1913
    return self._UnlockedGetHvparamsString(hvname)
1914

    
1915
  def _UnlockedGetNodeList(self):
1916
    """Return the list of nodes which are in the configuration.
1917

1918
    This function is for internal use, when the config lock is already
1919
    held.
1920

1921
    @rtype: list
1922

1923
    """
1924
    return self._config_data.nodes.keys()
1925

    
1926
  @locking.ssynchronized(_config_lock, shared=1)
1927
  def GetNodeList(self):
1928
    """Return the list of nodes which are in the configuration.
1929

1930
    """
1931
    return self._UnlockedGetNodeList()
1932

    
1933
  def _UnlockedGetOnlineNodeList(self):
1934
    """Return the list of nodes which are online.
1935

1936
    """
1937
    all_nodes = [self._UnlockedGetNodeInfo(node)
1938
                 for node in self._UnlockedGetNodeList()]
1939
    return [node.uuid for node in all_nodes if not node.offline]
1940

    
1941
  @locking.ssynchronized(_config_lock, shared=1)
1942
  def GetOnlineNodeList(self):
1943
    """Return the list of nodes which are online.
1944

1945
    """
1946
    return self._UnlockedGetOnlineNodeList()
1947

    
1948
  @locking.ssynchronized(_config_lock, shared=1)
1949
  def GetVmCapableNodeList(self):
1950
    """Return the list of nodes which are not vm capable.
1951

1952
    """
1953
    all_nodes = [self._UnlockedGetNodeInfo(node)
1954
                 for node in self._UnlockedGetNodeList()]
1955
    return [node.uuid for node in all_nodes if node.vm_capable]
1956

    
1957
  @locking.ssynchronized(_config_lock, shared=1)
1958
  def GetNonVmCapableNodeList(self):
1959
    """Return the list of nodes which are not vm capable.
1960

1961
    """
1962
    all_nodes = [self._UnlockedGetNodeInfo(node)
1963
                 for node in self._UnlockedGetNodeList()]
1964
    return [node.uuid for node in all_nodes if not node.vm_capable]
1965

    
1966
  @locking.ssynchronized(_config_lock, shared=1)
1967
  def GetMultiNodeInfo(self, node_uuids):
1968
    """Get the configuration of multiple nodes.
1969

1970
    @param node_uuids: list of node UUIDs
1971
    @rtype: list
1972
    @return: list of tuples of (node, node_info), where node_info is
1973
        what would GetNodeInfo return for the node, in the original
1974
        order
1975

1976
    """
1977
    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
1978

    
1979
  def _UnlockedGetAllNodesInfo(self):
1980
    """Gets configuration of all nodes.
1981

1982
    @note: See L{GetAllNodesInfo}
1983

1984
    """
1985
    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
1986
                 for node_uuid in self._UnlockedGetNodeList()])
1987

    
1988
  @locking.ssynchronized(_config_lock, shared=1)
1989
  def GetAllNodesInfo(self):
1990
    """Get the configuration of all nodes.
1991

1992
    @rtype: dict
1993
    @return: dict of (node, node_info), where node_info is what
1994
              would GetNodeInfo return for the node
1995

1996
    """
1997
    return self._UnlockedGetAllNodesInfo()
1998

    
1999
  def _UnlockedGetNodeInfoByName(self, node_name):
2000
    for node in self._UnlockedGetAllNodesInfo().values():
2001
      if node.name == node_name:
2002
        return node
2003
    return None
2004

    
2005
  @locking.ssynchronized(_config_lock, shared=1)
2006
  def GetNodeInfoByName(self, node_name):
2007
    """Get the L{objects.Node} object for a named node.
2008

2009
    @param node_name: name of the node to get information for
2010
    @type node_name: string
2011
    @return: the corresponding L{objects.Node} instance or None if no
2012
          information is available
2013

2014
    """
2015
    return self._UnlockedGetNodeInfoByName(node_name)
2016

    
2017
  def _UnlockedGetNodeName(self, node_spec):
2018
    if isinstance(node_spec, objects.Node):
2019
      return node_spec.name
2020
    elif isinstance(node_spec, basestring):
2021
      node_info = self._UnlockedGetNodeInfo(node_spec)
2022
      if node_info is None:
2023
        raise errors.OpExecError("Unknown node: %s" % node_spec)
2024
      return node_info.name
2025
    else:
2026
      raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2027

    
2028
  @locking.ssynchronized(_config_lock, shared=1)
2029
  def GetNodeName(self, node_spec):
2030
    """Gets the node name for the passed node.
2031

2032
    @param node_spec: node to get names for
2033
    @type node_spec: either node UUID or a L{objects.Node} object
2034
    @rtype: string
2035
    @return: node name
2036

2037
    """
2038
    return self._UnlockedGetNodeName(node_spec)
2039

    
2040
  def _UnlockedGetNodeNames(self, node_specs):
2041
    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2042

    
2043
  @locking.ssynchronized(_config_lock, shared=1)
2044
  def GetNodeNames(self, node_specs):
2045
    """Gets the node names for the passed list of nodes.
2046

2047
    @param node_specs: list of nodes to get names for
2048
    @type node_specs: list of either node UUIDs or L{objects.Node} objects
2049
    @rtype: list of strings
2050
    @return: list of node names
2051

2052
    """
2053
    return self._UnlockedGetNodeNames(node_specs)
2054

    
2055
  @locking.ssynchronized(_config_lock, shared=1)
2056
  def GetNodeGroupsFromNodes(self, node_uuids):
2057
    """Returns groups for a list of nodes.
2058

2059
    @type node_uuids: list of string
2060
    @param node_uuids: List of node UUIDs
2061
    @rtype: frozenset
2062

2063
    """
2064
    return frozenset(self._UnlockedGetNodeInfo(uuid).group
2065
                     for uuid in node_uuids)
2066

    
2067
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2068
    """Get the number of current and maximum desired and possible candidates.
2069

2070
    @type exceptions: list
2071
    @param exceptions: if passed, list of nodes that should be ignored
2072
    @rtype: tuple
2073
    @return: tuple of (current, desired and possible, possible)
2074

2075
    """
2076
    mc_now = mc_should = mc_max = 0
2077
    for node in self._config_data.nodes.values():
2078
      if exceptions and node.uuid in exceptions:
2079
        continue
2080
      if not (node.offline or node.drained) and node.master_capable:
2081
        mc_max += 1
2082
      if node.master_candidate:
2083
        mc_now += 1
2084
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2085
    return (mc_now, mc_should, mc_max)
2086

    
2087
  @locking.ssynchronized(_config_lock, shared=1)
2088
  def GetMasterCandidateStats(self, exceptions=None):
2089
    """Get the number of current and maximum possible candidates.
2090

2091
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2092

2093
    @type exceptions: list
2094
    @param exceptions: if passed, list of nodes that should be ignored
2095
    @rtype: tuple
2096
    @return: tuple of (current, max)
2097

2098
    """
2099
    return self._UnlockedGetMasterCandidateStats(exceptions)
2100

    
2101
  @locking.ssynchronized(_config_lock)
2102
  def MaintainCandidatePool(self, exception_node_uuids):
2103
    """Try to grow the candidate pool to the desired size.
2104

2105
    @type exception_node_uuids: list
2106
    @param exception_node_uuids: if passed, list of nodes that should be ignored
2107
    @rtype: list
2108
    @return: list with the adjusted nodes (L{objects.Node} instances)
2109

2110
    """
2111
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2112
                          exception_node_uuids)
2113
    mod_list = []
2114
    if mc_now < mc_max:
2115
      node_list = self._config_data.nodes.keys()
2116
      random.shuffle(node_list)
2117
      for uuid in node_list:
2118
        if mc_now >= mc_max:
2119
          break
2120
        node = self._config_data.nodes[uuid]
2121
        if (node.master_candidate or node.offline or node.drained or
2122
            node.uuid in exception_node_uuids or not node.master_capable):
2123
          continue
2124
        mod_list.append(node)
2125
        node.master_candidate = True
2126
        node.serial_no += 1
2127
        mc_now += 1
2128
      if mc_now != mc_max:
2129
        # this should not happen
2130
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
2131
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
2132
      if mod_list:
2133
        self._config_data.cluster.serial_no += 1
2134
        self._WriteConfig()
2135

    
2136
    return mod_list
2137

    
2138
  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2139
    """Add a given node to the specified group.
2140

2141
    """
2142
    if nodegroup_uuid not in self._config_data.nodegroups:
2143
      # This can happen if a node group gets deleted between its lookup and
2144
      # when we're adding the first node to it, since we don't keep a lock in
2145
      # the meantime. It's ok though, as we'll fail cleanly if the node group
2146
      # is not found anymore.
2147
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2148
    if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2149
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2150

    
2151
  def _UnlockedRemoveNodeFromGroup(self, node):
2152
    """Remove a given node from its group.
2153

2154
    """
2155
    nodegroup = node.group
2156
    if nodegroup not in self._config_data.nodegroups:
2157
      logging.warning("Warning: node '%s' has unknown node group '%s'"
2158
                      " (while being removed from it)", node.uuid, nodegroup)
2159
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
2160
    if node.uuid not in nodegroup_obj.members:
2161
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
2162
                      " (while being removed from it)", node.uuid, nodegroup)
2163
    else:
2164
      nodegroup_obj.members.remove(node.uuid)
2165

    
2166
  @locking.ssynchronized(_config_lock)
2167
  def AssignGroupNodes(self, mods):
2168
    """Changes the group of a number of nodes.
2169

2170
    @type mods: list of tuples; (node name, new group UUID)
2171
    @param mods: Node membership modifications
2172

2173
    """
2174
    groups = self._config_data.nodegroups
2175
    nodes = self._config_data.nodes
2176

    
2177
    resmod = []
2178

    
2179
    # Try to resolve UUIDs first
2180
    for (node_uuid, new_group_uuid) in mods:
2181
      try:
2182
        node = nodes[node_uuid]
2183
      except KeyError:
2184
        raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2185

    
2186
      if node.group == new_group_uuid:
2187
        # Node is being assigned to its current group
2188
        logging.debug("Node '%s' was assigned to its current group (%s)",
2189
                      node_uuid, node.group)
2190
        continue
2191

    
2192
      # Try to find current group of node
2193
      try:
2194
        old_group = groups[node.group]
2195
      except KeyError:
2196
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2197
                                        node.group)
2198

    
2199
      # Try to find new group for node
2200
      try:
2201
        new_group = groups[new_group_uuid]
2202
      except KeyError:
2203
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2204
                                        new_group_uuid)
2205

    
2206
      assert node.uuid in old_group.members, \
2207
        ("Inconsistent configuration: node '%s' not listed in members for its"
2208
         " old group '%s'" % (node.uuid, old_group.uuid))
2209
      assert node.uuid not in new_group.members, \
2210
        ("Inconsistent configuration: node '%s' already listed in members for"
2211
         " its new group '%s'" % (node.uuid, new_group.uuid))
2212

    
2213
      resmod.append((node, old_group, new_group))
2214

    
2215
    # Apply changes
2216
    for (node, old_group, new_group) in resmod:
2217
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2218
        "Assigning to current group is not possible"
2219

    
2220
      node.group = new_group.uuid
2221

    
2222
      # Update members of involved groups
2223
      if node.uuid in old_group.members:
2224
        old_group.members.remove(node.uuid)
2225
      if node.uuid not in new_group.members:
2226
        new_group.members.append(node.uuid)
2227

    
2228
    # Update timestamps and serials (only once per node/group object)
2229
    now = time.time()
2230
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2231
      obj.serial_no += 1
2232
      obj.mtime = now
2233

    
2234
    # Force ssconf update
2235
    self._config_data.cluster.serial_no += 1
2236

    
2237
    self._WriteConfig()
2238

    
2239
  def _BumpSerialNo(self):
2240
    """Bump up the serial number of the config.
2241

2242
    """
2243
    self._config_data.serial_no += 1
2244
    self._config_data.mtime = time.time()
2245

    
2246
  def _AllUUIDObjects(self):
2247
    """Returns all objects with uuid attributes.
2248

2249
    """
2250
    return (self._config_data.instances.values() +
2251
            self._config_data.nodes.values() +
2252
            self._config_data.nodegroups.values() +
2253
            self._config_data.networks.values() +
2254
            self._AllDisks() +
2255
            self._AllNICs() +
2256
            [self._config_data.cluster])
2257

    
2258
  def _OpenConfig(self, accept_foreign):
2259
    """Read the config data from disk.
2260

2261
    """
2262
    raw_data = utils.ReadFile(self._cfg_file)
2263

    
2264
    try:
2265
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2266
    except Exception, err:
2267
      raise errors.ConfigurationError(err)
2268

    
2269
    # Make sure the configuration has the right version
2270
    _ValidateConfig(data)
2271

    
2272
    if (not hasattr(data, "cluster") or
2273
        not hasattr(data.cluster, "rsahostkeypub")):
2274
      raise errors.ConfigurationError("Incomplete configuration"
2275
                                      " (missing cluster.rsahostkeypub)")
2276

    
2277
    if not data.cluster.master_node in data.nodes:
2278
      msg = ("The configuration denotes node %s as master, but does not"
2279
             " contain information about this node" %
2280
             data.cluster.master_node)
2281
      raise errors.ConfigurationError(msg)
2282

    
2283
    master_info = data.nodes[data.cluster.master_node]
2284
    if master_info.name != self._my_hostname and not accept_foreign:
2285
      msg = ("The configuration denotes node %s as master, while my"
2286
             " hostname is %s; opening a foreign configuration is only"
2287
             " possible in accept_foreign mode" %
2288
             (master_info.name, self._my_hostname))
2289
      raise errors.ConfigurationError(msg)
2290

    
2291
    self._config_data = data
2292
    # reset the last serial as -1 so that the next write will cause
2293
    # ssconf update
2294
    self._last_cluster_serial = -1
2295

    
2296
    # Upgrade configuration if needed
2297
    self._UpgradeConfig()
2298

    
2299
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2300

    
2301
  def _UpgradeConfig(self):
2302
    """Run any upgrade steps.
2303

2304
    This method performs both in-object upgrades and also update some data
2305
    elements that need uniqueness across the whole configuration or interact
2306
    with other objects.
2307

2308
    @warning: this function will call L{_WriteConfig()}, but also
2309
        L{DropECReservations} so it needs to be called only from a
2310
        "safe" place (the constructor). If one wanted to call it with
2311
        the lock held, a DropECReservationUnlocked would need to be
2312
        created first, to avoid causing deadlock.
2313

2314
    """
2315
    # Keep a copy of the persistent part of _config_data to check for changes
2316
    # Serialization doesn't guarantee order in dictionaries
2317
    oldconf = copy.deepcopy(self._config_data.ToDict())
2318

    
2319
    # In-object upgrades
2320
    self._config_data.UpgradeConfig()
2321

    
2322
    for item in self._AllUUIDObjects():
2323
      if item.uuid is None:
2324
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2325
    if not self._config_data.nodegroups:
2326
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2327
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2328
                                            members=[])
2329
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2330
    for node in self._config_data.nodes.values():
2331
      if not node.group:
2332
        node.group = self.LookupNodeGroup(None)
2333
      # This is technically *not* an upgrade, but needs to be done both when
2334
      # nodegroups are being added, and upon normally loading the config,
2335
      # because the members list of a node group is discarded upon
2336
      # serializing/deserializing the object.
2337
      self._UnlockedAddNodeToGroup(node.uuid, node.group)
2338

    
2339
    modified = (oldconf != self._config_data.ToDict())
2340
    if modified:
2341
      self._WriteConfig()
2342
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2343
      # only called at config init time, without the lock held
2344
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2345
    else:
2346
      config_errors = self._UnlockedVerifyConfig()
2347
      if config_errors:
2348
        errmsg = ("Loaded configuration data is not consistent: %s" %
2349
                  (utils.CommaJoin(config_errors)))
2350
        logging.critical(errmsg)
2351

    
2352
  def _DistributeConfig(self, feedback_fn):
2353
    """Distribute the configuration to the other nodes.
2354

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

2358
    """
2359
    if self._offline:
2360
      return True
2361

    
2362
    bad = False
2363

    
2364
    node_list = []
2365
    addr_list = []
2366
    myhostname = self._my_hostname
2367
    # we can skip checking whether _UnlockedGetNodeInfo returns None
2368
    # since the node list comes from _UnlocketGetNodeList, and we are
2369
    # called with the lock held, so no modifications should take place
2370
    # in between
2371
    for node_uuid in self._UnlockedGetNodeList():
2372
      node_info = self._UnlockedGetNodeInfo(node_uuid)
2373
      if node_info.name == myhostname or not node_info.master_candidate:
2374
        continue
2375
      node_list.append(node_info.name)
2376
      addr_list.append(node_info.primary_ip)
2377

    
2378
    # TODO: Use dedicated resolver talking to config writer for name resolution
2379
    result = \
2380
      self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2381
    for to_node, to_result in result.items():
2382
      msg = to_result.fail_msg
2383
      if msg:
2384
        msg = ("Copy of file %s to node %s failed: %s" %
2385
               (self._cfg_file, to_node, msg))
2386
        logging.error(msg)
2387

    
2388
        if feedback_fn:
2389
          feedback_fn(msg)
2390

    
2391
        bad = True
2392

    
2393
    return not bad
2394

    
2395
  def _WriteConfig(self, destination=None, feedback_fn=None):
2396
    """Write the configuration data to persistent storage.
2397

2398
    """
2399
    assert feedback_fn is None or callable(feedback_fn)
2400

    
2401
    # Warn on config errors, but don't abort the save - the
2402
    # configuration has already been modified, and we can't revert;
2403
    # the best we can do is to warn the user and save as is, leaving
2404
    # recovery to the user
2405
    config_errors = self._UnlockedVerifyConfig()
2406
    if config_errors:
2407
      errmsg = ("Configuration data is not consistent: %s" %
2408
                (utils.CommaJoin(config_errors)))
2409
      logging.critical(errmsg)
2410
      if feedback_fn:
2411
        feedback_fn(errmsg)
2412

    
2413
    if destination is None:
2414
      destination = self._cfg_file
2415
    self._BumpSerialNo()
2416
    txt = serializer.Dump(self._config_data.ToDict())
2417

    
2418
    getents = self._getents()
2419
    try:
2420
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2421
                               close=False, gid=getents.confd_gid, mode=0640)
2422
    except errors.LockError:
2423
      raise errors.ConfigurationError("The configuration file has been"
2424
                                      " modified since the last write, cannot"
2425
                                      " update")
2426
    try:
2427
      self._cfg_id = utils.GetFileID(fd=fd)
2428
    finally:
2429
      os.close(fd)
2430

    
2431
    self.write_count += 1
2432

    
2433
    # and redistribute the config file to master candidates
2434
    self._DistributeConfig(feedback_fn)
2435

    
2436
    # Write ssconf files on all nodes (including locally)
2437
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
2438
      if not self._offline:
2439
        result = self._GetRpc(None).call_write_ssconf_files(
2440
          self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2441
          self._UnlockedGetSsconfValues())
2442

    
2443
        for nname, nresu in result.items():
2444
          msg = nresu.fail_msg
2445
          if msg:
2446
            errmsg = ("Error while uploading ssconf files to"
2447
                      " node %s: %s" % (nname, msg))
2448
            logging.warning(errmsg)
2449

    
2450
            if feedback_fn:
2451
              feedback_fn(errmsg)
2452

    
2453
      self._last_cluster_serial = self._config_data.cluster.serial_no
2454

    
2455
  def _GetAllHvparamsStrings(self, hypervisors):
2456
    """Get the hvparams of all given hypervisors from the config.
2457

2458
    @type hypervisors: list of string
2459
    @param hypervisors: list of hypervisor names
2460
    @rtype: dict of strings
2461
    @returns: dictionary mapping the hypervisor name to a string representation
2462
      of the hypervisor's hvparams
2463

2464
    """
2465
    hvparams = {}
2466
    for hv in hypervisors:
2467
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2468
    return hvparams
2469

    
2470
  @staticmethod
2471
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2472
    """Extends the ssconf_values dictionary by hvparams.
2473

2474
    @type ssconf_values: dict of strings
2475
    @param ssconf_values: dictionary mapping ssconf_keys to strings
2476
      representing the content of ssconf files
2477
    @type all_hvparams: dict of strings
2478
    @param all_hvparams: dictionary mapping hypervisor names to a string
2479
      representation of their hvparams
2480
    @rtype: same as ssconf_values
2481
    @returns: the ssconf_values dictionary extended by hvparams
2482

2483
    """
2484
    for hv in all_hvparams:
2485
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2486
      ssconf_values[ssconf_key] = all_hvparams[hv]
2487
    return ssconf_values
2488

    
2489
  def _UnlockedGetSsconfValues(self):
2490
    """Return the values needed by ssconf.
2491

2492
    @rtype: dict
2493
    @return: a dictionary with keys the ssconf names and values their
2494
        associated value
2495

2496
    """
2497
    fn = "\n".join
2498
    instance_names = utils.NiceSort(
2499
                       [inst.name for inst in
2500
                        self._UnlockedGetAllInstancesInfo().values()])
2501
    node_infos = self._UnlockedGetAllNodesInfo().values()
2502
    node_names = [node.name for node in node_infos]
2503
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2504
                    for ninfo in node_infos]
2505
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2506
                    for ninfo in node_infos]
2507

    
2508
    instance_data = fn(instance_names)
2509
    off_data = fn(node.name for node in node_infos if node.offline)
2510
    on_data = fn(node.name for node in node_infos if not node.offline)
2511
    mc_data = fn(node.name for node in node_infos if node.master_candidate)
2512
    mc_ips_data = fn(node.primary_ip for node in node_infos
2513
                     if node.master_candidate)
2514
    node_data = fn(node_names)
2515
    node_pri_ips_data = fn(node_pri_ips)
2516
    node_snd_ips_data = fn(node_snd_ips)
2517

    
2518
    cluster = self._config_data.cluster
2519
    cluster_tags = fn(cluster.GetTags())
2520

    
2521
    hypervisor_list = fn(cluster.enabled_hypervisors)
2522
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2523

    
2524
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2525

    
2526
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2527
                  self._config_data.nodegroups.values()]
2528
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2529
    networks = ["%s %s" % (net.uuid, net.name) for net in
2530
                self._config_data.networks.values()]
2531
    networks_data = fn(utils.NiceSort(networks))
2532

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

    
2568
  @locking.ssynchronized(_config_lock, shared=1)
2569
  def GetSsconfValues(self):
2570
    """Wrapper using lock around _UnlockedGetSsconf().
2571

2572
    """
2573
    return self._UnlockedGetSsconfValues()
2574

    
2575
  @locking.ssynchronized(_config_lock, shared=1)
2576
  def GetVGName(self):
2577
    """Return the volume group name.
2578

2579
    """
2580
    return self._config_data.cluster.volume_group_name
2581

    
2582
  @locking.ssynchronized(_config_lock)
2583
  def SetVGName(self, vg_name):
2584
    """Set the volume group name.
2585

2586
    """
2587
    self._config_data.cluster.volume_group_name = vg_name
2588
    self._config_data.cluster.serial_no += 1
2589
    self._WriteConfig()
2590

    
2591
  @locking.ssynchronized(_config_lock, shared=1)
2592
  def GetDRBDHelper(self):
2593
    """Return DRBD usermode helper.
2594

2595
    """
2596
    return self._config_data.cluster.drbd_usermode_helper
2597

    
2598
  @locking.ssynchronized(_config_lock)
2599
  def SetDRBDHelper(self, drbd_helper):
2600
    """Set DRBD usermode helper.
2601

2602
    """
2603
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2604
    self._config_data.cluster.serial_no += 1
2605
    self._WriteConfig()
2606

    
2607
  @locking.ssynchronized(_config_lock, shared=1)
2608
  def GetMACPrefix(self):
2609
    """Return the mac prefix.
2610

2611
    """
2612
    return self._config_data.cluster.mac_prefix
2613

    
2614
  @locking.ssynchronized(_config_lock, shared=1)
2615
  def GetClusterInfo(self):
2616
    """Returns information about the cluster
2617

2618
    @rtype: L{objects.Cluster}
2619
    @return: the cluster object
2620

2621
    """
2622
    return self._config_data.cluster
2623

    
2624
  @locking.ssynchronized(_config_lock, shared=1)
2625
  def HasAnyDiskOfType(self, dev_type):
2626
    """Check if in there is at disk of the given type in the configuration.
2627

2628
    """
2629
    return self._config_data.HasAnyDiskOfType(dev_type)
2630

    
2631
  @locking.ssynchronized(_config_lock)
2632
  def Update(self, target, feedback_fn, ec_id=None):
2633
    """Notify function to be called after updates.
2634

2635
    This function must be called when an object (as returned by
2636
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2637
    caller wants the modifications saved to the backing store. Note
2638
    that all modified objects will be saved, but the target argument
2639
    is the one the caller wants to ensure that it's saved.
2640

2641
    @param target: an instance of either L{objects.Cluster},
2642
        L{objects.Node} or L{objects.Instance} which is existing in
2643
        the cluster
2644
    @param feedback_fn: Callable feedback function
2645

2646
    """
2647
    if self._config_data is None:
2648
      raise errors.ProgrammerError("Configuration file not read,"
2649
                                   " cannot save.")
2650
    update_serial = False
2651
    if isinstance(target, objects.Cluster):
2652
      test = target == self._config_data.cluster
2653
    elif isinstance(target, objects.Node):
2654
      test = target in self._config_data.nodes.values()
2655
      update_serial = True
2656
    elif isinstance(target, objects.Instance):
2657
      test = target in self._config_data.instances.values()
2658
    elif isinstance(target, objects.NodeGroup):
2659
      test = target in self._config_data.nodegroups.values()
2660
    elif isinstance(target, objects.Network):
2661
      test = target in self._config_data.networks.values()
2662
    else:
2663
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2664
                                   " ConfigWriter.Update" % type(target))
2665
    if not test:
2666
      raise errors.ConfigurationError("Configuration updated since object"
2667
                                      " has been read or unknown object")
2668
    target.serial_no += 1
2669
    target.mtime = now = time.time()
2670

    
2671
    if update_serial:
2672
      # for node updates, we need to increase the cluster serial too
2673
      self._config_data.cluster.serial_no += 1
2674
      self._config_data.cluster.mtime = now
2675

    
2676
    if isinstance(target, objects.Instance):
2677
      self._UnlockedReleaseDRBDMinors(target.uuid)
2678

    
2679
    if ec_id is not None:
2680
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2681
      self._UnlockedCommitTemporaryIps(ec_id)
2682

    
2683
    self._WriteConfig(feedback_fn=feedback_fn)
2684

    
2685
  @locking.ssynchronized(_config_lock)
2686
  def DropECReservations(self, ec_id):
2687
    """Drop per-execution-context reservations
2688

2689
    """
2690
    for rm in self._all_rms:
2691
      rm.DropECReservations(ec_id)
2692

    
2693
  @locking.ssynchronized(_config_lock, shared=1)
2694
  def GetAllNetworksInfo(self):
2695
    """Get configuration info of all the networks.
2696

2697
    """
2698
    return dict(self._config_data.networks)
2699

    
2700
  def _UnlockedGetNetworkList(self):
2701
    """Get the list of networks.
2702

2703
    This function is for internal use, when the config lock is already held.
2704

2705
    """
2706
    return self._config_data.networks.keys()
2707

    
2708
  @locking.ssynchronized(_config_lock, shared=1)
2709
  def GetNetworkList(self):
2710
    """Get the list of networks.
2711

2712
    @return: array of networks, ex. ["main", "vlan100", "200]
2713

2714
    """
2715
    return self._UnlockedGetNetworkList()
2716

    
2717
  @locking.ssynchronized(_config_lock, shared=1)
2718
  def GetNetworkNames(self):
2719
    """Get a list of network names
2720

2721
    """
2722
    names = [net.name
2723
             for net in self._config_data.networks.values()]
2724
    return names
2725

    
2726
  def _UnlockedGetNetwork(self, uuid):
2727
    """Returns information about a network.
2728

2729
    This function is for internal use, when the config lock is already held.
2730

2731
    """
2732
    if uuid not in self._config_data.networks:
2733
      return None
2734

    
2735
    return self._config_data.networks[uuid]
2736

    
2737
  @locking.ssynchronized(_config_lock, shared=1)
2738
  def GetNetwork(self, uuid):
2739
    """Returns information about a network.
2740

2741
    It takes the information from the configuration file.
2742

2743
    @param uuid: UUID of the network
2744

2745
    @rtype: L{objects.Network}
2746
    @return: the network object
2747

2748
    """
2749
    return self._UnlockedGetNetwork(uuid)
2750

    
2751
  @locking.ssynchronized(_config_lock)
2752
  def AddNetwork(self, net, ec_id, check_uuid=True):
2753
    """Add a network to the configuration.
2754

2755
    @type net: L{objects.Network}
2756
    @param net: the Network object to add
2757
    @type ec_id: string
2758
    @param ec_id: unique id for the job to use when creating a missing UUID
2759

2760
    """
2761
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2762
    self._WriteConfig()
2763

    
2764
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2765
    """Add a network to the configuration.
2766

2767
    """
2768
    logging.info("Adding network %s to configuration", net.name)
2769

    
2770
    if check_uuid:
2771
      self._EnsureUUID(net, ec_id)
2772

    
2773
    net.serial_no = 1
2774
    net.ctime = net.mtime = time.time()
2775
    self._config_data.networks[net.uuid] = net
2776
    self._config_data.cluster.serial_no += 1
2777

    
2778
  def _UnlockedLookupNetwork(self, target):
2779
    """Lookup a network's UUID.
2780

2781
    @type target: string
2782
    @param target: network name or UUID
2783
    @rtype: string
2784
    @return: network UUID
2785
    @raises errors.OpPrereqError: when the target network cannot be found
2786

2787
    """
2788
    if target is None:
2789
      return None
2790
    if target in self._config_data.networks:
2791
      return target
2792
    for net in self._config_data.networks.values():
2793
      if net.name == target:
2794
        return net.uuid
2795
    raise errors.OpPrereqError("Network '%s' not found" % target,
2796
                               errors.ECODE_NOENT)
2797

    
2798
  @locking.ssynchronized(_config_lock, shared=1)
2799
  def LookupNetwork(self, target):
2800
    """Lookup a network's UUID.
2801

2802
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2803

2804
    @type target: string
2805
    @param target: network name or UUID
2806
    @rtype: string
2807
    @return: network UUID
2808

2809
    """
2810
    return self._UnlockedLookupNetwork(target)
2811

    
2812
  @locking.ssynchronized(_config_lock)
2813
  def RemoveNetwork(self, network_uuid):
2814
    """Remove a network from the configuration.
2815

2816
    @type network_uuid: string
2817
    @param network_uuid: the UUID of the network to remove
2818

2819
    """
2820
    logging.info("Removing network %s from configuration", network_uuid)
2821

    
2822
    if network_uuid not in self._config_data.networks:
2823
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2824

    
2825
    del self._config_data.networks[network_uuid]
2826
    self._config_data.cluster.serial_no += 1
2827
    self._WriteConfig()
2828

    
2829
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2830
    """Get the netparams (mode, link) of a network.
2831

2832
    Get a network's netparams for a given node.
2833

2834
    @type net_uuid: string
2835
    @param net_uuid: network uuid
2836
    @type node_uuid: string
2837
    @param node_uuid: node UUID
2838
    @rtype: dict or None
2839
    @return: netparams
2840

2841
    """
2842
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2843
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2844
    netparams = nodegroup_info.networks.get(net_uuid, None)
2845

    
2846
    return netparams
2847

    
2848
  @locking.ssynchronized(_config_lock, shared=1)
2849
  def GetGroupNetParams(self, net_uuid, node_uuid):
2850
    """Locking wrapper of _UnlockedGetGroupNetParams()
2851

2852
    """
2853
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2854

    
2855
  @locking.ssynchronized(_config_lock, shared=1)
2856
  def CheckIPInNodeGroup(self, ip, node_uuid):
2857
    """Check IP uniqueness in nodegroup.
2858

2859
    Check networks that are connected in the node's node group
2860
    if ip is contained in any of them. Used when creating/adding
2861
    a NIC to ensure uniqueness among nodegroups.
2862

2863
    @type ip: string
2864
    @param ip: ip address
2865
    @type node_uuid: string
2866
    @param node_uuid: node UUID
2867
    @rtype: (string, dict) or (None, None)
2868
    @return: (network name, netparams)
2869

2870
    """
2871
    if ip is None:
2872
      return (None, None)
2873
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2874
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2875
    for net_uuid in nodegroup_info.networks.keys():
2876
      net_info = self._UnlockedGetNetwork(net_uuid)
2877
      pool = network.AddressPool(net_info)
2878
      if pool.Contains(ip):
2879
        return (net_info.name, nodegroup_info.networks[net_uuid])
2880

    
2881
    return (None, None)