Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 294254b1

History | View | Annotate | Download (94.9 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
    if constants.DT_RBD in cluster.diskparams:
671
      access = cluster.diskparams[constants.DT_RBD][constants.RBD_ACCESS]
672
      if access not in constants.DISK_VALID_ACCESS_MODES:
673
        result.append(
674
          "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
675
            constants.DT_RBD, constants.RBD_ACCESS, access,
676
            utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
677
          )
678
        )
679

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

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

    
711
      # parameter checks
712
      if instance.beparams:
713
        _helper("instance %s" % instance.name, "beparams",
714
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
715

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

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

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

    
742
        result.append("Instance '%s' has wrongly named disks: %s" %
743
                      (instance.name, tmp))
744

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

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

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

    
766
    if not data.nodes[cluster.master_node].master_candidate:
767
      result.append("Master node is not a master candidate")
768

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

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

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

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

    
825
    # IP checks
826
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
827
    ips = {}
828

    
829
    def _AddIpAddress(ip, name):
830
      ips.setdefault(ip, []).append(name)
831

    
832
    _AddIpAddress(cluster.master_ip, "cluster_ip")
833

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

    
839
    for instance in data.instances.values():
840
      for idx, nic in enumerate(instance.nics):
841
        if nic.ip is None:
842
          continue
843

    
844
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
845
        nic_mode = nicparams[constants.NIC_MODE]
846
        nic_link = nicparams[constants.NIC_LINK]
847

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

    
855
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
856
                      "instance:%s/nic:%d" % (instance.name, idx))
857

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

    
863
    return result
864

    
865
  @locking.ssynchronized(_config_lock, shared=1)
866
  def VerifyConfig(self):
867
    """Verify function.
868

869
    This is just a wrapper over L{_UnlockedVerifyConfig}.
870

871
    @rtype: list
872
    @return: a list of error messages; a non-empty list signifies
873
        configuration errors
874

875
    """
876
    return self._UnlockedVerifyConfig()
877

    
878
  @locking.ssynchronized(_config_lock)
879
  def AddTcpUdpPort(self, port):
880
    """Adds a new port to the available port pool.
881

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

886
    """
887
    if not isinstance(port, int):
888
      raise errors.ProgrammerError("Invalid type passed for port")
889

    
890
    self._config_data.cluster.tcpudp_port_pool.add(port)
891

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

896
    """
897
    return self._config_data.cluster.tcpudp_port_pool.copy()
898

    
899
  @locking.ssynchronized(_config_lock)
900
  def AllocatePort(self):
901
    """Allocate a port.
902

903
    The port will be taken from the available port pool or from the
904
    default port range (and in this case we increase
905
    highest_used_port).
906

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

    
919
    self._WriteConfig()
920
    return port
921

    
922
  def _UnlockedComputeDRBDMap(self):
923
    """Compute the used DRBD minor/nodes.
924

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

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

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

    
966
  @locking.ssynchronized(_config_lock)
967
  def ComputeDRBDMap(self):
968
    """Compute the used DRBD minor/nodes.
969

970
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
971

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

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

    
983
  @locking.ssynchronized(_config_lock)
984
  def AllocateDRBDMinor(self, node_uuids, inst_uuid):
985
    """Allocate a drbd minor.
986

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

992
    @type inst_uuid: string
993
    @param inst_uuid: the instance for which we allocate minors
994

995
    """
996
    assert isinstance(inst_uuid, basestring), \
997
           "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
998

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

    
1039
  def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1040
    """Release temporary drbd minors allocated for a given instance.
1041

1042
    @type inst_uuid: string
1043
    @param inst_uuid: the instance for which temporary minors should be
1044
                      released
1045

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

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

1057
    This should be called on the error paths, on the success paths
1058
    it's automatically called by the ConfigWriter add and update
1059
    functions.
1060

1061
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1062

1063
    @type inst_uuid: string
1064
    @param inst_uuid: the instance for which temporary minors should be
1065
                      released
1066

1067
    """
1068
    self._UnlockedReleaseDRBDMinors(inst_uuid)
1069

    
1070
  @locking.ssynchronized(_config_lock, shared=1)
1071
  def GetConfigVersion(self):
1072
    """Get the configuration version.
1073

1074
    @return: Config version
1075

1076
    """
1077
    return self._config_data.version
1078

    
1079
  @locking.ssynchronized(_config_lock, shared=1)
1080
  def GetClusterName(self):
1081
    """Get cluster name.
1082

1083
    @return: Cluster name
1084

1085
    """
1086
    return self._config_data.cluster.cluster_name
1087

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

1092
    @return: Master node UUID
1093

1094
    """
1095
    return self._config_data.cluster.master_node
1096

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

1101
    @return: Master node hostname
1102

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

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

1110
    @rtype: objects.Node
1111
    @return: Master node L{objects.Node} object
1112

1113
    """
1114
    return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
1115

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

1120
    @return: Master IP
1121

1122
    """
1123
    return self._config_data.cluster.master_ip
1124

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

1129
    """
1130
    return self._config_data.cluster.master_netdev
1131

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

1136
    """
1137
    return self._config_data.cluster.master_netmask
1138

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

1143
    """
1144
    return self._config_data.cluster.use_external_mip_script
1145

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

1150
    """
1151
    return self._config_data.cluster.file_storage_dir
1152

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

1157
    """
1158
    return self._config_data.cluster.shared_file_storage_dir
1159

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

1164
    """
1165
    return self._config_data.cluster.enabled_hypervisors[0]
1166

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

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

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

    
1177
  @locking.ssynchronized(_config_lock, shared=1)
1178
  def GetDsaHostKey(self):
1179
    """Return the dsa hostkey from the config.
1180

1181
    @rtype: string
1182
    @return: the dsa hostkey
1183

1184
    """
1185
    return self._config_data.cluster.dsahostkeypub
1186

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

1191
    """
1192
    return self._config_data.cluster.default_iallocator
1193

    
1194
  @locking.ssynchronized(_config_lock, shared=1)
1195
  def GetPrimaryIPFamily(self):
1196
    """Get cluster primary ip family.
1197

1198
    @return: primary ip family
1199

1200
    """
1201
    return self._config_data.cluster.primary_ip_family
1202

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

1207
    @rtype: L{object.MasterNetworkParameters}
1208
    @return: network parameters of the master node
1209

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

    
1217
    return result
1218

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

1223
    This method calls group.UpgradeConfig() to fill any missing attributes
1224
    according to their default values.
1225

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

1235
    """
1236
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1237
    self._WriteConfig()
1238

    
1239
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1240
    """Add a node group to the configuration.
1241

1242
    """
1243
    logging.info("Adding node group %s to configuration", group.name)
1244

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

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

    
1261
    group.serial_no = 1
1262
    group.ctime = group.mtime = time.time()
1263
    group.UpgradeConfig()
1264

    
1265
    self._config_data.nodegroups[group.uuid] = group
1266
    self._config_data.cluster.serial_no += 1
1267

    
1268
  @locking.ssynchronized(_config_lock)
1269
  def RemoveNodeGroup(self, group_uuid):
1270
    """Remove a node group from the configuration.
1271

1272
    @type group_uuid: string
1273
    @param group_uuid: the UUID of the node group to remove
1274

1275
    """
1276
    logging.info("Removing node group %s from configuration", group_uuid)
1277

    
1278
    if group_uuid not in self._config_data.nodegroups:
1279
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1280

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

    
1284
    del self._config_data.nodegroups[group_uuid]
1285
    self._config_data.cluster.serial_no += 1
1286
    self._WriteConfig()
1287

    
1288
  def _UnlockedLookupNodeGroup(self, target):
1289
    """Lookup a node group's UUID.
1290

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

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

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

1316
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1317

1318
    @type target: string or None
1319
    @param target: group name or UUID or None to look for the default
1320
    @rtype: string
1321
    @return: nodegroup UUID
1322

1323
    """
1324
    return self._UnlockedLookupNodeGroup(target)
1325

    
1326
  def _UnlockedGetNodeGroup(self, uuid):
1327
    """Lookup a node group.
1328

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

1334
    """
1335
    if uuid not in self._config_data.nodegroups:
1336
      return None
1337

    
1338
    return self._config_data.nodegroups[uuid]
1339

    
1340
  @locking.ssynchronized(_config_lock, shared=1)
1341
  def GetNodeGroup(self, uuid):
1342
    """Lookup a node group.
1343

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

1349
    """
1350
    return self._UnlockedGetNodeGroup(uuid)
1351

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

1356
    """
1357
    return dict(self._config_data.nodegroups)
1358

    
1359
  @locking.ssynchronized(_config_lock, shared=1)
1360
  def GetNodeGroupList(self):
1361
    """Get a list of node groups.
1362

1363
    """
1364
    return self._config_data.nodegroups.keys()
1365

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

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

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

1381
    @param group_uuids: List of node group UUIDs
1382
    @rtype: list
1383
    @return: List of tuples of (group_uuid, group_info)
1384

1385
    """
1386
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1387

    
1388
  @locking.ssynchronized(_config_lock)
1389
  def AddInstance(self, instance, ec_id):
1390
    """Add an instance to the config.
1391

1392
    This should be used after creating a new instance.
1393

1394
    @type instance: L{objects.Instance}
1395
    @param instance: the instance object
1396

1397
    """
1398
    if not isinstance(instance, objects.Instance):
1399
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1400

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

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

    
1412
    self._CheckUniqueUUID(instance, include_temporary=False)
1413

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

    
1422
  def _EnsureUUID(self, item, ec_id):
1423
    """Ensures a given object has a valid UUID.
1424

1425
    @param item: the instance or node to be checked
1426
    @param ec_id: the execution context id for the uuid reservation
1427

1428
    """
1429
    if not item.uuid:
1430
      item.uuid = self._GenerateUniqueID(ec_id)
1431
    else:
1432
      self._CheckUniqueUUID(item, include_temporary=True)
1433

    
1434
  def _CheckUniqueUUID(self, item, include_temporary):
1435
    """Checks that the UUID of the given object is unique.
1436

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

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

    
1449
  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1450
    """Set the instance's status to a given value.
1451

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

    
1458
    if status is None:
1459
      status = instance.admin_state
1460
    if disks_active is None:
1461
      disks_active = instance.disks_active
1462

    
1463
    assert status in constants.ADMINST_ALL, \
1464
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1465

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

    
1474
  @locking.ssynchronized(_config_lock)
1475
  def MarkInstanceUp(self, inst_uuid):
1476
    """Mark the instance status to up in the config.
1477

1478
    This also sets the instance disks active flag.
1479

1480
    """
1481
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1482

    
1483
  @locking.ssynchronized(_config_lock)
1484
  def MarkInstanceOffline(self, inst_uuid):
1485
    """Mark the instance status to down in the config.
1486

1487
    This also clears the instance disks active flag.
1488

1489
    """
1490
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1491

    
1492
  @locking.ssynchronized(_config_lock)
1493
  def RemoveInstance(self, inst_uuid):
1494
    """Remove the instance from the configuration.
1495

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

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

    
1507
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1508

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

    
1514
    del self._config_data.instances[inst_uuid]
1515
    self._config_data.cluster.serial_no += 1
1516
    self._WriteConfig()
1517

    
1518
  @locking.ssynchronized(_config_lock)
1519
  def RenameInstance(self, inst_uuid, new_name):
1520
    """Rename an instance.
1521

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

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

    
1530
    inst = self._config_data.instances[inst_uuid]
1531
    inst.name = new_name
1532

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

    
1541
    # Force update of ssconf files
1542
    self._config_data.cluster.serial_no += 1
1543

    
1544
    self._WriteConfig()
1545

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

1550
    This does not touch the instance disks active flag, as shut down instances
1551
    can still have active disks.
1552

1553
    """
1554
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1555

    
1556
  @locking.ssynchronized(_config_lock)
1557
  def MarkInstanceDisksActive(self, inst_uuid):
1558
    """Mark the status of instance disks active.
1559

1560
    """
1561
    self._SetInstanceStatus(inst_uuid, None, True)
1562

    
1563
  @locking.ssynchronized(_config_lock)
1564
  def MarkInstanceDisksInactive(self, inst_uuid):
1565
    """Mark the status of instance disks inactive.
1566

1567
    """
1568
    self._SetInstanceStatus(inst_uuid, None, False)
1569

    
1570
  def _UnlockedGetInstanceList(self):
1571
    """Get the list of instances.
1572

1573
    This function is for internal use, when the config lock is already held.
1574

1575
    """
1576
    return self._config_data.instances.keys()
1577

    
1578
  @locking.ssynchronized(_config_lock, shared=1)
1579
  def GetInstanceList(self):
1580
    """Get the list of instances.
1581

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

1584
    """
1585
    return self._UnlockedGetInstanceList()
1586

    
1587
  def ExpandInstanceName(self, short_name):
1588
    """Attempt to expand an incomplete instance name.
1589

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

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

    
1603
  def _UnlockedGetInstanceInfo(self, inst_uuid):
1604
    """Returns information about an instance.
1605

1606
    This function is for internal use, when the config lock is already held.
1607

1608
    """
1609
    if inst_uuid not in self._config_data.instances:
1610
      return None
1611

    
1612
    return self._config_data.instances[inst_uuid]
1613

    
1614
  @locking.ssynchronized(_config_lock, shared=1)
1615
  def GetInstanceInfo(self, inst_uuid):
1616
    """Returns information about an instance.
1617

1618
    It takes the information from the configuration file. Other information of
1619
    an instance are taken from the live systems.
1620

1621
    @param inst_uuid: UUID of the instance
1622

1623
    @rtype: L{objects.Instance}
1624
    @return: the instance object
1625

1626
    """
1627
    return self._UnlockedGetInstanceInfo(inst_uuid)
1628

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

1633
    @rtype: frozenset
1634

1635
    """
1636
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1637
    if not instance:
1638
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1639

    
1640
    if primary_only:
1641
      nodes = [instance.primary_node]
1642
    else:
1643
      nodes = instance.all_nodes
1644

    
1645
    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1646
                     for node_uuid in nodes)
1647

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

1652
    @rtype: frozenset
1653

1654
    """
1655
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1656
    if not instance:
1657
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1658

    
1659
    networks = set()
1660
    for nic in instance.nics:
1661
      if nic.network:
1662
        networks.add(nic.network)
1663

    
1664
    return frozenset(networks)
1665

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

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

1676
    """
1677
    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1678

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

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

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

    
1696
  @locking.ssynchronized(_config_lock, shared=1)
1697
  def GetAllInstancesInfo(self):
1698
    """Get the configuration of all instances.
1699

1700
    @rtype: dict
1701
    @return: dict of (instance, instance_info), where instance_info is what
1702
              would GetInstanceInfo return for the node
1703

1704
    """
1705
    return self._UnlockedGetAllInstancesInfo()
1706

    
1707
  def _UnlockedGetAllInstancesInfo(self):
1708
    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1709
                    for inst_uuid in self._UnlockedGetInstanceList()])
1710
    return my_dict
1711

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

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

1723
    """
1724
    return dict((uuid, inst)
1725
                for (uuid, inst) in self._config_data.instances.items()
1726
                if filter_fn(inst))
1727

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

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

1737
    """
1738
    return self._UnlockedGetInstanceInfoByName(inst_name)
1739

    
1740
  def _UnlockedGetInstanceInfoByName(self, inst_name):
1741
    for inst in self._UnlockedGetAllInstancesInfo().values():
1742
      if inst.name == inst_name:
1743
        return inst
1744
    return None
1745

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

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

1756
    @param inst_uuid: instance UUID to get name for
1757
    @type inst_uuid: string
1758
    @rtype: string
1759
    @return: instance name
1760

1761
    """
1762
    return self._UnlockedGetInstanceName(inst_uuid)
1763

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

1768
    @param inst_uuids: list of instance UUIDs to get names for
1769
    @type inst_uuids: list of strings
1770
    @rtype: list of strings
1771
    @return: list of instance names
1772

1773
    """
1774
    return self._UnlockedGetInstanceNames(inst_uuids)
1775

    
1776
  def _UnlockedGetInstanceNames(self, inst_uuids):
1777
    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1778

    
1779
  @locking.ssynchronized(_config_lock)
1780
  def AddNode(self, node, ec_id):
1781
    """Add a node to the configuration.
1782

1783
    @type node: L{objects.Node}
1784
    @param node: a Node instance
1785

1786
    """
1787
    logging.info("Adding node %s to configuration", node.name)
1788

    
1789
    self._EnsureUUID(node, ec_id)
1790

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

    
1798
  @locking.ssynchronized(_config_lock)
1799
  def RemoveNode(self, node_uuid):
1800
    """Remove a node from the configuration.
1801

1802
    """
1803
    logging.info("Removing node %s from configuration", node_uuid)
1804

    
1805
    if node_uuid not in self._config_data.nodes:
1806
      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1807

    
1808
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1809
    del self._config_data.nodes[node_uuid]
1810
    self._config_data.cluster.serial_no += 1
1811
    self._WriteConfig()
1812

    
1813
  def ExpandNodeName(self, short_name):
1814
    """Attempt to expand an incomplete node name into a node UUID.
1815

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

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

    
1829
  def _UnlockedGetNodeInfo(self, node_uuid):
1830
    """Get the configuration of a node, as stored in the config.
1831

1832
    This function is for internal use, when the config lock is already
1833
    held.
1834

1835
    @param node_uuid: the node UUID
1836

1837
    @rtype: L{objects.Node}
1838
    @return: the node object
1839

1840
    """
1841
    if node_uuid not in self._config_data.nodes:
1842
      return None
1843

    
1844
    return self._config_data.nodes[node_uuid]
1845

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

1850
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1851

1852
    @param node_uuid: the node UUID
1853

1854
    @rtype: L{objects.Node}
1855
    @return: the node object
1856

1857
    """
1858
    return self._UnlockedGetNodeInfo(node_uuid)
1859

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

1864
    @param node_uuid: the node UUID
1865

1866
    @rtype: (list, list)
1867
    @return: a tuple with two lists: the primary and the secondary instances
1868

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

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

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

1888
    """
1889
    if primary_only:
1890
      nodes_fn = lambda inst: [inst.primary_node]
1891
    else:
1892
      nodes_fn = lambda inst: inst.all_nodes
1893

    
1894
    return frozenset(inst.uuid
1895
                     for inst in self._config_data.instances.values()
1896
                     for node_uuid in nodes_fn(inst)
1897
                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1898

    
1899
  def _UnlockedGetHvparamsString(self, hvname):
1900
    """Return the string representation of the list of hyervisor parameters of
1901
    the given hypervisor.
1902

1903
    @see: C{GetHvparams}
1904

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

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

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

1922
    """
1923
    return self._UnlockedGetHvparamsString(hvname)
1924

    
1925
  def _UnlockedGetNodeList(self):
1926
    """Return the list of nodes which are in the configuration.
1927

1928
    This function is for internal use, when the config lock is already
1929
    held.
1930

1931
    @rtype: list
1932

1933
    """
1934
    return self._config_data.nodes.keys()
1935

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

1940
    """
1941
    return self._UnlockedGetNodeList()
1942

    
1943
  def _UnlockedGetOnlineNodeList(self):
1944
    """Return the list of nodes which are online.
1945

1946
    """
1947
    all_nodes = [self._UnlockedGetNodeInfo(node)
1948
                 for node in self._UnlockedGetNodeList()]
1949
    return [node.uuid for node in all_nodes if not node.offline]
1950

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

1955
    """
1956
    return self._UnlockedGetOnlineNodeList()
1957

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

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

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

1971
    """
1972
    all_nodes = [self._UnlockedGetNodeInfo(node)
1973
                 for node in self._UnlockedGetNodeList()]
1974
    return [node.uuid for node in all_nodes if not node.vm_capable]
1975

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

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

1986
    """
1987
    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
1988

    
1989
  def _UnlockedGetAllNodesInfo(self):
1990
    """Gets configuration of all nodes.
1991

1992
    @note: See L{GetAllNodesInfo}
1993

1994
    """
1995
    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
1996
                 for node_uuid in self._UnlockedGetNodeList()])
1997

    
1998
  @locking.ssynchronized(_config_lock, shared=1)
1999
  def GetAllNodesInfo(self):
2000
    """Get the configuration of all nodes.
2001

2002
    @rtype: dict
2003
    @return: dict of (node, node_info), where node_info is what
2004
              would GetNodeInfo return for the node
2005

2006
    """
2007
    return self._UnlockedGetAllNodesInfo()
2008

    
2009
  def _UnlockedGetNodeInfoByName(self, node_name):
2010
    for node in self._UnlockedGetAllNodesInfo().values():
2011
      if node.name == node_name:
2012
        return node
2013
    return None
2014

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

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

2024
    """
2025
    return self._UnlockedGetNodeInfoByName(node_name)
2026

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

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

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

2047
    """
2048
    return self._UnlockedGetNodeName(node_spec)
2049

    
2050
  def _UnlockedGetNodeNames(self, node_specs):
2051
    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2052

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

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

2062
    """
2063
    return self._UnlockedGetNodeNames(node_specs)
2064

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

2069
    @type node_uuids: list of string
2070
    @param node_uuids: List of node UUIDs
2071
    @rtype: frozenset
2072

2073
    """
2074
    return frozenset(self._UnlockedGetNodeInfo(uuid).group
2075
                     for uuid in node_uuids)
2076

    
2077
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2078
    """Get the number of current and maximum desired and possible candidates.
2079

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

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

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

2101
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2102

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

2108
    """
2109
    return self._UnlockedGetMasterCandidateStats(exceptions)
2110

    
2111
  @locking.ssynchronized(_config_lock)
2112
  def MaintainCandidatePool(self, exception_node_uuids):
2113
    """Try to grow the candidate pool to the desired size.
2114

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

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

    
2146
    return mod_list
2147

    
2148
  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2149
    """Add a given node to the specified group.
2150

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

    
2161
  def _UnlockedRemoveNodeFromGroup(self, node):
2162
    """Remove a given node from its group.
2163

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

    
2176
  @locking.ssynchronized(_config_lock)
2177
  def AssignGroupNodes(self, mods):
2178
    """Changes the group of a number of nodes.
2179

2180
    @type mods: list of tuples; (node name, new group UUID)
2181
    @param mods: Node membership modifications
2182

2183
    """
2184
    groups = self._config_data.nodegroups
2185
    nodes = self._config_data.nodes
2186

    
2187
    resmod = []
2188

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

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

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

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

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

    
2223
      resmod.append((node, old_group, new_group))
2224

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

    
2230
      node.group = new_group.uuid
2231

    
2232
      # Update members of involved groups
2233
      if node.uuid in old_group.members:
2234
        old_group.members.remove(node.uuid)
2235
      if node.uuid not in new_group.members:
2236
        new_group.members.append(node.uuid)
2237

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

    
2244
    # Force ssconf update
2245
    self._config_data.cluster.serial_no += 1
2246

    
2247
    self._WriteConfig()
2248

    
2249
  def _BumpSerialNo(self):
2250
    """Bump up the serial number of the config.
2251

2252
    """
2253
    self._config_data.serial_no += 1
2254
    self._config_data.mtime = time.time()
2255

    
2256
  def _AllUUIDObjects(self):
2257
    """Returns all objects with uuid attributes.
2258

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

    
2268
  def _OpenConfig(self, accept_foreign):
2269
    """Read the config data from disk.
2270

2271
    """
2272
    raw_data = utils.ReadFile(self._cfg_file)
2273

    
2274
    try:
2275
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2276
    except Exception, err:
2277
      raise errors.ConfigurationError(err)
2278

    
2279
    # Make sure the configuration has the right version
2280
    _ValidateConfig(data)
2281

    
2282
    if (not hasattr(data, "cluster") or
2283
        not hasattr(data.cluster, "rsahostkeypub")):
2284
      raise errors.ConfigurationError("Incomplete configuration"
2285
                                      " (missing cluster.rsahostkeypub)")
2286

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

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

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

    
2306
    # Upgrade configuration if needed
2307
    self._UpgradeConfig()
2308

    
2309
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2310

    
2311
  def _UpgradeConfig(self):
2312
    """Run any upgrade steps.
2313

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

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

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

    
2329
    # In-object upgrades
2330
    self._config_data.UpgradeConfig()
2331

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

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

    
2362
  def _DistributeConfig(self, feedback_fn):
2363
    """Distribute the configuration to the other nodes.
2364

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

2368
    """
2369
    if self._offline:
2370
      return True
2371

    
2372
    bad = False
2373

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

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

    
2398
        if feedback_fn:
2399
          feedback_fn(msg)
2400

    
2401
        bad = True
2402

    
2403
    return not bad
2404

    
2405
  def _WriteConfig(self, destination=None, feedback_fn=None):
2406
    """Write the configuration data to persistent storage.
2407

2408
    """
2409
    assert feedback_fn is None or callable(feedback_fn)
2410

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

    
2423
    if destination is None:
2424
      destination = self._cfg_file
2425
    self._BumpSerialNo()
2426
    txt = serializer.Dump(self._config_data.ToDict())
2427

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

    
2441
    self.write_count += 1
2442

    
2443
    # and redistribute the config file to master candidates
2444
    self._DistributeConfig(feedback_fn)
2445

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

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

    
2460
            if feedback_fn:
2461
              feedback_fn(errmsg)
2462

    
2463
      self._last_cluster_serial = self._config_data.cluster.serial_no
2464

    
2465
  def _GetAllHvparamsStrings(self, hypervisors):
2466
    """Get the hvparams of all given hypervisors from the config.
2467

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

2474
    """
2475
    hvparams = {}
2476
    for hv in hypervisors:
2477
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2478
    return hvparams
2479

    
2480
  @staticmethod
2481
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2482
    """Extends the ssconf_values dictionary by hvparams.
2483

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

2493
    """
2494
    for hv in all_hvparams:
2495
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2496
      ssconf_values[ssconf_key] = all_hvparams[hv]
2497
    return ssconf_values
2498

    
2499
  def _UnlockedGetSsconfValues(self):
2500
    """Return the values needed by ssconf.
2501

2502
    @rtype: dict
2503
    @return: a dictionary with keys the ssconf names and values their
2504
        associated value
2505

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

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

    
2528
    cluster = self._config_data.cluster
2529
    cluster_tags = fn(cluster.GetTags())
2530

    
2531
    hypervisor_list = fn(cluster.enabled_hypervisors)
2532
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2533

    
2534
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2535

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

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

    
2578
  @locking.ssynchronized(_config_lock, shared=1)
2579
  def GetSsconfValues(self):
2580
    """Wrapper using lock around _UnlockedGetSsconf().
2581

2582
    """
2583
    return self._UnlockedGetSsconfValues()
2584

    
2585
  @locking.ssynchronized(_config_lock, shared=1)
2586
  def GetVGName(self):
2587
    """Return the volume group name.
2588

2589
    """
2590
    return self._config_data.cluster.volume_group_name
2591

    
2592
  @locking.ssynchronized(_config_lock)
2593
  def SetVGName(self, vg_name):
2594
    """Set the volume group name.
2595

2596
    """
2597
    self._config_data.cluster.volume_group_name = vg_name
2598
    self._config_data.cluster.serial_no += 1
2599
    self._WriteConfig()
2600

    
2601
  @locking.ssynchronized(_config_lock, shared=1)
2602
  def GetDRBDHelper(self):
2603
    """Return DRBD usermode helper.
2604

2605
    """
2606
    return self._config_data.cluster.drbd_usermode_helper
2607

    
2608
  @locking.ssynchronized(_config_lock)
2609
  def SetDRBDHelper(self, drbd_helper):
2610
    """Set DRBD usermode helper.
2611

2612
    """
2613
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2614
    self._config_data.cluster.serial_no += 1
2615
    self._WriteConfig()
2616

    
2617
  @locking.ssynchronized(_config_lock, shared=1)
2618
  def GetMACPrefix(self):
2619
    """Return the mac prefix.
2620

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

    
2624
  @locking.ssynchronized(_config_lock, shared=1)
2625
  def GetClusterInfo(self):
2626
    """Returns information about the cluster
2627

2628
    @rtype: L{objects.Cluster}
2629
    @return: the cluster object
2630

2631
    """
2632
    return self._config_data.cluster
2633

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

2638
    """
2639
    return self._config_data.HasAnyDiskOfType(dev_type)
2640

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

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

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

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

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

    
2686
    if isinstance(target, objects.Instance):
2687
      self._UnlockedReleaseDRBDMinors(target.uuid)
2688

    
2689
    if ec_id is not None:
2690
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2691
      self._UnlockedCommitTemporaryIps(ec_id)
2692

    
2693
    self._WriteConfig(feedback_fn=feedback_fn)
2694

    
2695
  @locking.ssynchronized(_config_lock)
2696
  def DropECReservations(self, ec_id):
2697
    """Drop per-execution-context reservations
2698

2699
    """
2700
    for rm in self._all_rms:
2701
      rm.DropECReservations(ec_id)
2702

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

2707
    """
2708
    return dict(self._config_data.networks)
2709

    
2710
  def _UnlockedGetNetworkList(self):
2711
    """Get the list of networks.
2712

2713
    This function is for internal use, when the config lock is already held.
2714

2715
    """
2716
    return self._config_data.networks.keys()
2717

    
2718
  @locking.ssynchronized(_config_lock, shared=1)
2719
  def GetNetworkList(self):
2720
    """Get the list of networks.
2721

2722
    @return: array of networks, ex. ["main", "vlan100", "200]
2723

2724
    """
2725
    return self._UnlockedGetNetworkList()
2726

    
2727
  @locking.ssynchronized(_config_lock, shared=1)
2728
  def GetNetworkNames(self):
2729
    """Get a list of network names
2730

2731
    """
2732
    names = [net.name
2733
             for net in self._config_data.networks.values()]
2734
    return names
2735

    
2736
  def _UnlockedGetNetwork(self, uuid):
2737
    """Returns information about a network.
2738

2739
    This function is for internal use, when the config lock is already held.
2740

2741
    """
2742
    if uuid not in self._config_data.networks:
2743
      return None
2744

    
2745
    return self._config_data.networks[uuid]
2746

    
2747
  @locking.ssynchronized(_config_lock, shared=1)
2748
  def GetNetwork(self, uuid):
2749
    """Returns information about a network.
2750

2751
    It takes the information from the configuration file.
2752

2753
    @param uuid: UUID of the network
2754

2755
    @rtype: L{objects.Network}
2756
    @return: the network object
2757

2758
    """
2759
    return self._UnlockedGetNetwork(uuid)
2760

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

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

2770
    """
2771
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2772
    self._WriteConfig()
2773

    
2774
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2775
    """Add a network to the configuration.
2776

2777
    """
2778
    logging.info("Adding network %s to configuration", net.name)
2779

    
2780
    if check_uuid:
2781
      self._EnsureUUID(net, ec_id)
2782

    
2783
    net.serial_no = 1
2784
    net.ctime = net.mtime = time.time()
2785
    self._config_data.networks[net.uuid] = net
2786
    self._config_data.cluster.serial_no += 1
2787

    
2788
  def _UnlockedLookupNetwork(self, target):
2789
    """Lookup a network's UUID.
2790

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

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

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

2812
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2813

2814
    @type target: string
2815
    @param target: network name or UUID
2816
    @rtype: string
2817
    @return: network UUID
2818

2819
    """
2820
    return self._UnlockedLookupNetwork(target)
2821

    
2822
  @locking.ssynchronized(_config_lock)
2823
  def RemoveNetwork(self, network_uuid):
2824
    """Remove a network from the configuration.
2825

2826
    @type network_uuid: string
2827
    @param network_uuid: the UUID of the network to remove
2828

2829
    """
2830
    logging.info("Removing network %s from configuration", network_uuid)
2831

    
2832
    if network_uuid not in self._config_data.networks:
2833
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2834

    
2835
    del self._config_data.networks[network_uuid]
2836
    self._config_data.cluster.serial_no += 1
2837
    self._WriteConfig()
2838

    
2839
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2840
    """Get the netparams (mode, link) of a network.
2841

2842
    Get a network's netparams for a given node.
2843

2844
    @type net_uuid: string
2845
    @param net_uuid: network uuid
2846
    @type node_uuid: string
2847
    @param node_uuid: node UUID
2848
    @rtype: dict or None
2849
    @return: netparams
2850

2851
    """
2852
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2853
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2854
    netparams = nodegroup_info.networks.get(net_uuid, None)
2855

    
2856
    return netparams
2857

    
2858
  @locking.ssynchronized(_config_lock, shared=1)
2859
  def GetGroupNetParams(self, net_uuid, node_uuid):
2860
    """Locking wrapper of _UnlockedGetGroupNetParams()
2861

2862
    """
2863
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2864

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

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

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

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

    
2891
    return (None, None)