Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 710a2863

History | View | Annotate | Download (98.3 kB)

1
#
2
#
3

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

    
21

    
22
"""Configuration management for Ganeti
23

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

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

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

32
"""
33

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

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

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

    
57

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

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

    
63

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

67
  This only verifies the version of the configuration.
68

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

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

    
76

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

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

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

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

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

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

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

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

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

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

126
    """
127
    assert callable(generate_one_fn)
128

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

    
142

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

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

    
149

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

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

159
  """
160
  result = []
161

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

    
167
  return result
168

    
169

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

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

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

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

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

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

216
    """
217
    self._context = context
218

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
294
    return prefix
295

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

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

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

    
312
    return GenMac
313

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

318
    This should check the current instances for duplicates.
319

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

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

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

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

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

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

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

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

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

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

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

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

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

374
    This is just a wrapper around _UnlockedReleaseIp.
375

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

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

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

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

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

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

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

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

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

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

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

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

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

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

444
    This checks the current disks for duplicates.
445

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
546
    return result
547

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

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

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

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

    
568
    return result
569

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
891
    return result
892

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

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

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

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

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

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

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

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

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

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

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

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

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

    
947
    self._WriteConfig()
948
    return port
949

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1102
    @return: Config version
1103

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

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

1111
    @return: Cluster name
1112

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

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

1120
    @return: Master node UUID
1121

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

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

1129
    @return: Master node hostname
1130

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

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

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

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

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

1148
    @return: Master IP
1149

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1243
    @return: primary ip family
1244

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

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

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

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

    
1262
    return result
1263

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1394
    return self._config_data.nodegroups[uuid]
1395

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1549
    This also sets the instance disks active flag.
1550

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

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

1558
    This also clears the instance disks active flag.
1559

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

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

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

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

    
1578
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1579

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

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

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

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

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

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

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

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

    
1615
    self._WriteConfig()
1616

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1683
    return self._config_data.instances[inst_uuid]
1684

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

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

1692
    @param inst_uuid: UUID of the instance
1693

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

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

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

1704
    @rtype: frozenset
1705

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

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

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

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

1723
    @rtype: frozenset
1724

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

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

    
1735
    return frozenset(networks)
1736

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1860
    self._EnsureUUID(node, ec_id)
1861

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

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

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

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

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

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

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

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

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

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

1906
    @param node_uuid: the node UUID
1907

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

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

    
1915
    return self._config_data.nodes[node_uuid]
1916

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

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

1923
    @param node_uuid: the node UUID
1924

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

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

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

1935
    @param node_uuid: the node UUID
1936

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

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

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

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

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

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

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

1974
    @see: C{GetHvparams}
1975

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

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

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

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

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

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

2002
    @rtype: list
2003

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2063
    @note: See L{GetAllNodesInfo}
2064

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2232
    return mod_list
2233

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

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

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

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

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

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

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

    
2273
    resmod = []
2274

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

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

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

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

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

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

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

    
2316
      node.group = new_group.uuid
2317

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

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

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

    
2333
    self._WriteConfig()
2334

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

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

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

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

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

2357
    """
2358
    raw_data = utils.ReadFile(self._cfg_file)
2359

    
2360
    try:
2361
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2362
    except Exception, err:
2363
      raise errors.ConfigurationError(err)
2364

    
2365
    # Make sure the configuration has the right version
2366
    _ValidateConfig(data)
2367

    
2368
    if (not hasattr(data, "cluster") or
2369
        not hasattr(data.cluster, "rsahostkeypub")):
2370
      raise errors.ConfigurationError("Incomplete configuration"
2371
                                      " (missing cluster.rsahostkeypub)")
2372

    
2373
    if not data.cluster.master_node in data.nodes:
2374
      msg = ("The configuration denotes node %s as master, but does not"
2375
             " contain information about this node" %
2376
             data.cluster.master_node)
2377
      raise errors.ConfigurationError(msg)
2378

    
2379
    master_info = data.nodes[data.cluster.master_node]
2380
    if master_info.name != self._my_hostname and not accept_foreign:
2381
      msg = ("The configuration denotes node %s as master, while my"
2382
             " hostname is %s; opening a foreign configuration is only"
2383
             " possible in accept_foreign mode" %
2384
             (master_info.name, self._my_hostname))
2385
      raise errors.ConfigurationError(msg)
2386

    
2387
    self._config_data = data
2388
    # reset the last serial as -1 so that the next write will cause
2389
    # ssconf update
2390
    self._last_cluster_serial = -1
2391

    
2392
    # Upgrade configuration if needed
2393
    self._UpgradeConfig()
2394

    
2395
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2396

    
2397
  def _UpgradeConfig(self):
2398
    """Run any upgrade steps.
2399

2400
    This method performs both in-object upgrades and also update some data
2401
    elements that need uniqueness across the whole configuration or interact
2402
    with other objects.
2403

2404
    @warning: this function will call L{_WriteConfig()}, but also
2405
        L{DropECReservations} so it needs to be called only from a
2406
        "safe" place (the constructor). If one wanted to call it with
2407
        the lock held, a DropECReservationUnlocked would need to be
2408
        created first, to avoid causing deadlock.
2409

2410
    """
2411
    # Keep a copy of the persistent part of _config_data to check for changes
2412
    # Serialization doesn't guarantee order in dictionaries
2413
    oldconf = copy.deepcopy(self._config_data.ToDict())
2414

    
2415
    # In-object upgrades
2416
    self._config_data.UpgradeConfig()
2417

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

    
2435
    modified = (oldconf != self._config_data.ToDict())
2436
    if modified:
2437
      self._WriteConfig()
2438
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2439
      # only called at config init time, without the lock held
2440
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2441
    else:
2442
      config_errors = self._UnlockedVerifyConfig()
2443
      if config_errors:
2444
        errmsg = ("Loaded configuration data is not consistent: %s" %
2445
                  (utils.CommaJoin(config_errors)))
2446
        logging.critical(errmsg)
2447

    
2448
  def _DistributeConfig(self, feedback_fn):
2449
    """Distribute the configuration to the other nodes.
2450

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

2454
    """
2455
    if self._offline:
2456
      return True
2457

    
2458
    bad = False
2459

    
2460
    node_list = []
2461
    addr_list = []
2462
    myhostname = self._my_hostname
2463
    # we can skip checking whether _UnlockedGetNodeInfo returns None
2464
    # since the node list comes from _UnlocketGetNodeList, and we are
2465
    # called with the lock held, so no modifications should take place
2466
    # in between
2467
    for node_uuid in self._UnlockedGetNodeList():
2468
      node_info = self._UnlockedGetNodeInfo(node_uuid)
2469
      if node_info.name == myhostname or not node_info.master_candidate:
2470
        continue
2471
      node_list.append(node_info.name)
2472
      addr_list.append(node_info.primary_ip)
2473

    
2474
    # TODO: Use dedicated resolver talking to config writer for name resolution
2475
    result = \
2476
      self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2477
    for to_node, to_result in result.items():
2478
      msg = to_result.fail_msg
2479
      if msg:
2480
        msg = ("Copy of file %s to node %s failed: %s" %
2481
               (self._cfg_file, to_node, msg))
2482
        logging.error(msg)
2483

    
2484
        if feedback_fn:
2485
          feedback_fn(msg)
2486

    
2487
        bad = True
2488

    
2489
    return not bad
2490

    
2491
  def _WriteConfig(self, destination=None, feedback_fn=None):
2492
    """Write the configuration data to persistent storage.
2493

2494
    """
2495
    assert feedback_fn is None or callable(feedback_fn)
2496

    
2497
    # Warn on config errors, but don't abort the save - the
2498
    # configuration has already been modified, and we can't revert;
2499
    # the best we can do is to warn the user and save as is, leaving
2500
    # recovery to the user
2501
    config_errors = self._UnlockedVerifyConfig()
2502
    if config_errors:
2503
      errmsg = ("Configuration data is not consistent: %s" %
2504
                (utils.CommaJoin(config_errors)))
2505
      logging.critical(errmsg)
2506
      if feedback_fn:
2507
        feedback_fn(errmsg)
2508

    
2509
    if destination is None:
2510
      destination = self._cfg_file
2511
    self._BumpSerialNo()
2512
    txt = serializer.DumpJson(
2513
      self._config_data.ToDict(_with_private=True),
2514
      private_encoder=serializer.EncodeWithPrivateFields
2515
    )
2516

    
2517
    getents = self._getents()
2518
    try:
2519
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2520
                               close=False, gid=getents.confd_gid, mode=0640)
2521
    except errors.LockError:
2522
      raise errors.ConfigurationError("The configuration file has been"
2523
                                      " modified since the last write, cannot"
2524
                                      " update")
2525
    try:
2526
      self._cfg_id = utils.GetFileID(fd=fd)
2527
    finally:
2528
      os.close(fd)
2529

    
2530
    self.write_count += 1
2531

    
2532
    # and redistribute the config file to master candidates
2533
    self._DistributeConfig(feedback_fn)
2534

    
2535
    # Write ssconf files on all nodes (including locally)
2536
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
2537
      if not self._offline:
2538
        result = self._GetRpc(None).call_write_ssconf_files(
2539
          self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2540
          self._UnlockedGetSsconfValues())
2541

    
2542
        for nname, nresu in result.items():
2543
          msg = nresu.fail_msg
2544
          if msg:
2545
            errmsg = ("Error while uploading ssconf files to"
2546
                      " node %s: %s" % (nname, msg))
2547
            logging.warning(errmsg)
2548

    
2549
            if feedback_fn:
2550
              feedback_fn(errmsg)
2551

    
2552
      self._last_cluster_serial = self._config_data.cluster.serial_no
2553

    
2554
  def _GetAllHvparamsStrings(self, hypervisors):
2555
    """Get the hvparams of all given hypervisors from the config.
2556

2557
    @type hypervisors: list of string
2558
    @param hypervisors: list of hypervisor names
2559
    @rtype: dict of strings
2560
    @returns: dictionary mapping the hypervisor name to a string representation
2561
      of the hypervisor's hvparams
2562

2563
    """
2564
    hvparams = {}
2565
    for hv in hypervisors:
2566
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2567
    return hvparams
2568

    
2569
  @staticmethod
2570
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2571
    """Extends the ssconf_values dictionary by hvparams.
2572

2573
    @type ssconf_values: dict of strings
2574
    @param ssconf_values: dictionary mapping ssconf_keys to strings
2575
      representing the content of ssconf files
2576
    @type all_hvparams: dict of strings
2577
    @param all_hvparams: dictionary mapping hypervisor names to a string
2578
      representation of their hvparams
2579
    @rtype: same as ssconf_values
2580
    @returns: the ssconf_values dictionary extended by hvparams
2581

2582
    """
2583
    for hv in all_hvparams:
2584
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2585
      ssconf_values[ssconf_key] = all_hvparams[hv]
2586
    return ssconf_values
2587

    
2588
  def _UnlockedGetSsconfValues(self):
2589
    """Return the values needed by ssconf.
2590

2591
    @rtype: dict
2592
    @return: a dictionary with keys the ssconf names and values their
2593
        associated value
2594

2595
    """
2596
    fn = "\n".join
2597
    instance_names = utils.NiceSort(
2598
                       [inst.name for inst in
2599
                        self._UnlockedGetAllInstancesInfo().values()])
2600
    node_infos = self._UnlockedGetAllNodesInfo().values()
2601
    node_names = [node.name for node in node_infos]
2602
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2603
                    for ninfo in node_infos]
2604
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2605
                    for ninfo in node_infos]
2606

    
2607
    instance_data = fn(instance_names)
2608
    off_data = fn(node.name for node in node_infos if node.offline)
2609
    on_data = fn(node.name for node in node_infos if not node.offline)
2610
    mc_data = fn(node.name for node in node_infos if node.master_candidate)
2611
    mc_ips_data = fn(node.primary_ip for node in node_infos
2612
                     if node.master_candidate)
2613
    node_data = fn(node_names)
2614
    node_pri_ips_data = fn(node_pri_ips)
2615
    node_snd_ips_data = fn(node_snd_ips)
2616

    
2617
    cluster = self._config_data.cluster
2618
    cluster_tags = fn(cluster.GetTags())
2619

    
2620
    master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert)
2621
                                 for mc_uuid, mc_cert
2622
                                 in cluster.candidate_certs.items())
2623

    
2624
    hypervisor_list = fn(cluster.enabled_hypervisors)
2625
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2626

    
2627
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2628

    
2629
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2630
                  self._config_data.nodegroups.values()]
2631
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2632
    networks = ["%s %s" % (net.uuid, net.name) for net in
2633
                self._config_data.networks.values()]
2634
    networks_data = fn(utils.NiceSort(networks))
2635

    
2636
    ssconf_values = {
2637
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
2638
      constants.SS_CLUSTER_TAGS: cluster_tags,
2639
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2640
      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2641
      constants.SS_GLUSTER_STORAGE_DIR: cluster.gluster_storage_dir,
2642
      constants.SS_MASTER_CANDIDATES: mc_data,
2643
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2644
      constants.SS_MASTER_CANDIDATES_CERTS: master_candidates_certs,
2645
      constants.SS_MASTER_IP: cluster.master_ip,
2646
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
2647
      constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2648
      constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2649
      constants.SS_NODE_LIST: node_data,
2650
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2651
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2652
      constants.SS_OFFLINE_NODES: off_data,
2653
      constants.SS_ONLINE_NODES: on_data,
2654
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2655
      constants.SS_INSTANCE_LIST: instance_data,
2656
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2657
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
2658
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2659
      constants.SS_UID_POOL: uid_pool,
2660
      constants.SS_NODEGROUPS: nodegroups_data,
2661
      constants.SS_NETWORKS: networks_data,
2662
      }
2663
    ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2664
                                                     all_hvparams)
2665
    bad_values = [(k, v) for k, v in ssconf_values.items()
2666
                  if not isinstance(v, (str, basestring))]
2667
    if bad_values:
2668
      err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2669
      raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2670
                                      " values: %s" % err)
2671
    return ssconf_values
2672

    
2673
  @locking.ssynchronized(_config_lock, shared=1)
2674
  def GetSsconfValues(self):
2675
    """Wrapper using lock around _UnlockedGetSsconf().
2676

2677
    """
2678
    return self._UnlockedGetSsconfValues()
2679

    
2680
  @locking.ssynchronized(_config_lock, shared=1)
2681
  def GetVGName(self):
2682
    """Return the volume group name.
2683

2684
    """
2685
    return self._config_data.cluster.volume_group_name
2686

    
2687
  @locking.ssynchronized(_config_lock)
2688
  def SetVGName(self, vg_name):
2689
    """Set the volume group name.
2690

2691
    """
2692
    self._config_data.cluster.volume_group_name = vg_name
2693
    self._config_data.cluster.serial_no += 1
2694
    self._WriteConfig()
2695

    
2696
  @locking.ssynchronized(_config_lock, shared=1)
2697
  def GetDRBDHelper(self):
2698
    """Return DRBD usermode helper.
2699

2700
    """
2701
    return self._config_data.cluster.drbd_usermode_helper
2702

    
2703
  @locking.ssynchronized(_config_lock)
2704
  def SetDRBDHelper(self, drbd_helper):
2705
    """Set DRBD usermode helper.
2706

2707
    """
2708
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2709
    self._config_data.cluster.serial_no += 1
2710
    self._WriteConfig()
2711

    
2712
  @locking.ssynchronized(_config_lock, shared=1)
2713
  def GetMACPrefix(self):
2714
    """Return the mac prefix.
2715

2716
    """
2717
    return self._config_data.cluster.mac_prefix
2718

    
2719
  @locking.ssynchronized(_config_lock, shared=1)
2720
  def GetClusterInfo(self):
2721
    """Returns information about the cluster
2722

2723
    @rtype: L{objects.Cluster}
2724
    @return: the cluster object
2725

2726
    """
2727
    return self._config_data.cluster
2728

    
2729
  @locking.ssynchronized(_config_lock, shared=1)
2730
  def HasAnyDiskOfType(self, dev_type):
2731
    """Check if in there is at disk of the given type in the configuration.
2732

2733
    """
2734
    return self._config_data.HasAnyDiskOfType(dev_type)
2735

    
2736
  @locking.ssynchronized(_config_lock)
2737
  def Update(self, target, feedback_fn, ec_id=None):
2738
    """Notify function to be called after updates.
2739

2740
    This function must be called when an object (as returned by
2741
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2742
    caller wants the modifications saved to the backing store. Note
2743
    that all modified objects will be saved, but the target argument
2744
    is the one the caller wants to ensure that it's saved.
2745

2746
    @param target: an instance of either L{objects.Cluster},
2747
        L{objects.Node} or L{objects.Instance} which is existing in
2748
        the cluster
2749
    @param feedback_fn: Callable feedback function
2750

2751
    """
2752
    if self._config_data is None:
2753
      raise errors.ProgrammerError("Configuration file not read,"
2754
                                   " cannot save.")
2755
    update_serial = False
2756
    if isinstance(target, objects.Cluster):
2757
      test = target == self._config_data.cluster
2758
    elif isinstance(target, objects.Node):
2759
      test = target in self._config_data.nodes.values()
2760
      update_serial = True
2761
    elif isinstance(target, objects.Instance):
2762
      test = target in self._config_data.instances.values()
2763
    elif isinstance(target, objects.NodeGroup):
2764
      test = target in self._config_data.nodegroups.values()
2765
    elif isinstance(target, objects.Network):
2766
      test = target in self._config_data.networks.values()
2767
    else:
2768
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2769
                                   " ConfigWriter.Update" % type(target))
2770
    if not test:
2771
      raise errors.ConfigurationError("Configuration updated since object"
2772
                                      " has been read or unknown object")
2773
    target.serial_no += 1
2774
    target.mtime = now = time.time()
2775

    
2776
    if update_serial:
2777
      # for node updates, we need to increase the cluster serial too
2778
      self._config_data.cluster.serial_no += 1
2779
      self._config_data.cluster.mtime = now
2780

    
2781
    if isinstance(target, objects.Instance):
2782
      self._UnlockedReleaseDRBDMinors(target.uuid)
2783

    
2784
    if ec_id is not None:
2785
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2786
      self._UnlockedCommitTemporaryIps(ec_id)
2787

    
2788
    self._WriteConfig(feedback_fn=feedback_fn)
2789

    
2790
  @locking.ssynchronized(_config_lock)
2791
  def DropECReservations(self, ec_id):
2792
    """Drop per-execution-context reservations
2793

2794
    """
2795
    for rm in self._all_rms:
2796
      rm.DropECReservations(ec_id)
2797

    
2798
  @locking.ssynchronized(_config_lock, shared=1)
2799
  def GetAllNetworksInfo(self):
2800
    """Get configuration info of all the networks.
2801

2802
    """
2803
    return dict(self._config_data.networks)
2804

    
2805
  def _UnlockedGetNetworkList(self):
2806
    """Get the list of networks.
2807

2808
    This function is for internal use, when the config lock is already held.
2809

2810
    """
2811
    return self._config_data.networks.keys()
2812

    
2813
  @locking.ssynchronized(_config_lock, shared=1)
2814
  def GetNetworkList(self):
2815
    """Get the list of networks.
2816

2817
    @return: array of networks, ex. ["main", "vlan100", "200]
2818

2819
    """
2820
    return self._UnlockedGetNetworkList()
2821

    
2822
  @locking.ssynchronized(_config_lock, shared=1)
2823
  def GetNetworkNames(self):
2824
    """Get a list of network names
2825

2826
    """
2827
    names = [net.name
2828
             for net in self._config_data.networks.values()]
2829
    return names
2830

    
2831
  def _UnlockedGetNetwork(self, uuid):
2832
    """Returns information about a network.
2833

2834
    This function is for internal use, when the config lock is already held.
2835

2836
    """
2837
    if uuid not in self._config_data.networks:
2838
      return None
2839

    
2840
    return self._config_data.networks[uuid]
2841

    
2842
  @locking.ssynchronized(_config_lock, shared=1)
2843
  def GetNetwork(self, uuid):
2844
    """Returns information about a network.
2845

2846
    It takes the information from the configuration file.
2847

2848
    @param uuid: UUID of the network
2849

2850
    @rtype: L{objects.Network}
2851
    @return: the network object
2852

2853
    """
2854
    return self._UnlockedGetNetwork(uuid)
2855

    
2856
  @locking.ssynchronized(_config_lock)
2857
  def AddNetwork(self, net, ec_id, check_uuid=True):
2858
    """Add a network to the configuration.
2859

2860
    @type net: L{objects.Network}
2861
    @param net: the Network object to add
2862
    @type ec_id: string
2863
    @param ec_id: unique id for the job to use when creating a missing UUID
2864

2865
    """
2866
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2867
    self._WriteConfig()
2868

    
2869
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2870
    """Add a network to the configuration.
2871

2872
    """
2873
    logging.info("Adding network %s to configuration", net.name)
2874

    
2875
    if check_uuid:
2876
      self._EnsureUUID(net, ec_id)
2877

    
2878
    net.serial_no = 1
2879
    net.ctime = net.mtime = time.time()
2880
    self._config_data.networks[net.uuid] = net
2881
    self._config_data.cluster.serial_no += 1
2882

    
2883
  def _UnlockedLookupNetwork(self, target):
2884
    """Lookup a network's UUID.
2885

2886
    @type target: string
2887
    @param target: network name or UUID
2888
    @rtype: string
2889
    @return: network UUID
2890
    @raises errors.OpPrereqError: when the target network cannot be found
2891

2892
    """
2893
    if target is None:
2894
      return None
2895
    if target in self._config_data.networks:
2896
      return target
2897
    for net in self._config_data.networks.values():
2898
      if net.name == target:
2899
        return net.uuid
2900
    raise errors.OpPrereqError("Network '%s' not found" % target,
2901
                               errors.ECODE_NOENT)
2902

    
2903
  @locking.ssynchronized(_config_lock, shared=1)
2904
  def LookupNetwork(self, target):
2905
    """Lookup a network's UUID.
2906

2907
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2908

2909
    @type target: string
2910
    @param target: network name or UUID
2911
    @rtype: string
2912
    @return: network UUID
2913

2914
    """
2915
    return self._UnlockedLookupNetwork(target)
2916

    
2917
  @locking.ssynchronized(_config_lock)
2918
  def RemoveNetwork(self, network_uuid):
2919
    """Remove a network from the configuration.
2920

2921
    @type network_uuid: string
2922
    @param network_uuid: the UUID of the network to remove
2923

2924
    """
2925
    logging.info("Removing network %s from configuration", network_uuid)
2926

    
2927
    if network_uuid not in self._config_data.networks:
2928
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2929

    
2930
    del self._config_data.networks[network_uuid]
2931
    self._config_data.cluster.serial_no += 1
2932
    self._WriteConfig()
2933

    
2934
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2935
    """Get the netparams (mode, link) of a network.
2936

2937
    Get a network's netparams for a given node.
2938

2939
    @type net_uuid: string
2940
    @param net_uuid: network uuid
2941
    @type node_uuid: string
2942
    @param node_uuid: node UUID
2943
    @rtype: dict or None
2944
    @return: netparams
2945

2946
    """
2947
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2948
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2949
    netparams = nodegroup_info.networks.get(net_uuid, None)
2950

    
2951
    return netparams
2952

    
2953
  @locking.ssynchronized(_config_lock, shared=1)
2954
  def GetGroupNetParams(self, net_uuid, node_uuid):
2955
    """Locking wrapper of _UnlockedGetGroupNetParams()
2956

2957
    """
2958
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2959

    
2960
  @locking.ssynchronized(_config_lock, shared=1)
2961
  def CheckIPInNodeGroup(self, ip, node_uuid):
2962
    """Check IP uniqueness in nodegroup.
2963

2964
    Check networks that are connected in the node's node group
2965
    if ip is contained in any of them. Used when creating/adding
2966
    a NIC to ensure uniqueness among nodegroups.
2967

2968
    @type ip: string
2969
    @param ip: ip address
2970
    @type node_uuid: string
2971
    @param node_uuid: node UUID
2972
    @rtype: (string, dict) or (None, None)
2973
    @return: (network name, netparams)
2974

2975
    """
2976
    if ip is None:
2977
      return (None, None)
2978
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2979
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2980
    for net_uuid in nodegroup_info.networks.keys():
2981
      net_info = self._UnlockedGetNetwork(net_uuid)
2982
      pool = network.AddressPool(net_info)
2983
      if pool.Contains(ip):
2984
        return (net_info.name, nodegroup_info.networks[net_uuid])
2985

    
2986
    return (None, None)