Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ f1638b0b

History | View | Annotate | Download (97.9 kB)

1
#
2
#
3

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

    
21

    
22
"""Configuration management for Ganeti
23

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

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

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

32
"""
33

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

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

    
44
from ganeti import errors
45
from ganeti import locking
46
from ganeti import utils
47
from ganeti import constants
48
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)
1265
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1266
    """Add a node group to the configuration.
1267

1268
    This method calls group.UpgradeConfig() to fill any missing attributes
1269
    according to their default values.
1270

1271
    @type group: L{objects.NodeGroup}
1272
    @param group: the NodeGroup object to add
1273
    @type ec_id: string
1274
    @param ec_id: unique id for the job to use when creating a missing UUID
1275
    @type check_uuid: bool
1276
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1277
                       it does, ensure that it does not exist in the
1278
                       configuration already
1279

1280
    """
1281
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1282
    self._WriteConfig()
1283

    
1284
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1285
    """Add a node group to the configuration.
1286

1287
    """
1288
    logging.info("Adding node group %s to configuration", group.name)
1289

    
1290
    # Some code might need to add a node group with a pre-populated UUID
1291
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1292
    # the "does this UUID" exist already check.
1293
    if check_uuid:
1294
      self._EnsureUUID(group, ec_id)
1295

    
1296
    try:
1297
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1298
    except errors.OpPrereqError:
1299
      pass
1300
    else:
1301
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1302
                                 " node group (UUID: %s)" %
1303
                                 (group.name, existing_uuid),
1304
                                 errors.ECODE_EXISTS)
1305

    
1306
    group.serial_no = 1
1307
    group.ctime = group.mtime = time.time()
1308
    group.UpgradeConfig()
1309

    
1310
    self._config_data.nodegroups[group.uuid] = group
1311
    self._config_data.cluster.serial_no += 1
1312

    
1313
  @locking.ssynchronized(_config_lock)
1314
  def RemoveNodeGroup(self, group_uuid):
1315
    """Remove a node group from the configuration.
1316

1317
    @type group_uuid: string
1318
    @param group_uuid: the UUID of the node group to remove
1319

1320
    """
1321
    logging.info("Removing node group %s from configuration", group_uuid)
1322

    
1323
    if group_uuid not in self._config_data.nodegroups:
1324
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1325

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

    
1329
    del self._config_data.nodegroups[group_uuid]
1330
    self._config_data.cluster.serial_no += 1
1331
    self._WriteConfig()
1332

    
1333
  def _UnlockedLookupNodeGroup(self, target):
1334
    """Lookup a node group's UUID.
1335

1336
    @type target: string or None
1337
    @param target: group name or UUID or None to look for the default
1338
    @rtype: string
1339
    @return: nodegroup UUID
1340
    @raises errors.OpPrereqError: when the target group cannot be found
1341

1342
    """
1343
    if target is None:
1344
      if len(self._config_data.nodegroups) != 1:
1345
        raise errors.OpPrereqError("More than one node group exists. Target"
1346
                                   " group must be specified explicitly.")
1347
      else:
1348
        return self._config_data.nodegroups.keys()[0]
1349
    if target in self._config_data.nodegroups:
1350
      return target
1351
    for nodegroup in self._config_data.nodegroups.values():
1352
      if nodegroup.name == target:
1353
        return nodegroup.uuid
1354
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1355
                               errors.ECODE_NOENT)
1356

    
1357
  @locking.ssynchronized(_config_lock, shared=1)
1358
  def LookupNodeGroup(self, target):
1359
    """Lookup a node group's UUID.
1360

1361
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1362

1363
    @type target: string or None
1364
    @param target: group name or UUID or None to look for the default
1365
    @rtype: string
1366
    @return: nodegroup UUID
1367

1368
    """
1369
    return self._UnlockedLookupNodeGroup(target)
1370

    
1371
  def _UnlockedGetNodeGroup(self, uuid):
1372
    """Lookup a node group.
1373

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

1379
    """
1380
    if uuid not in self._config_data.nodegroups:
1381
      return None
1382

    
1383
    return self._config_data.nodegroups[uuid]
1384

    
1385
  @locking.ssynchronized(_config_lock, shared=1)
1386
  def GetNodeGroup(self, uuid):
1387
    """Lookup a node group.
1388

1389
    @type uuid: string
1390
    @param uuid: group UUID
1391
    @rtype: L{objects.NodeGroup} or None
1392
    @return: nodegroup object, or None if not found
1393

1394
    """
1395
    return self._UnlockedGetNodeGroup(uuid)
1396

    
1397
  def _UnlockedGetAllNodeGroupsInfo(self):
1398
    """Get the configuration of all node groups.
1399

1400
    """
1401
    return dict(self._config_data.nodegroups)
1402

    
1403
  @locking.ssynchronized(_config_lock, shared=1)
1404
  def GetAllNodeGroupsInfo(self):
1405
    """Get the configuration of all node groups.
1406

1407
    """
1408
    return self._UnlockedGetAllNodeGroupsInfo()
1409

    
1410
  @locking.ssynchronized(_config_lock, shared=1)
1411
  def GetAllNodeGroupsInfoDict(self):
1412
    """Get the configuration of all node groups expressed as a dictionary of
1413
    dictionaries.
1414

1415
    """
1416
    return dict(map(lambda (uuid, ng): (uuid, ng.ToDict()),
1417
                    self._UnlockedGetAllNodeGroupsInfo().items()))
1418

    
1419
  @locking.ssynchronized(_config_lock, shared=1)
1420
  def GetNodeGroupList(self):
1421
    """Get a list of node groups.
1422

1423
    """
1424
    return self._config_data.nodegroups.keys()
1425

    
1426
  @locking.ssynchronized(_config_lock, shared=1)
1427
  def GetNodeGroupMembersByNodes(self, nodes):
1428
    """Get nodes which are member in the same nodegroups as the given nodes.
1429

1430
    """
1431
    ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1432
    return frozenset(member_uuid
1433
                     for node_uuid in nodes
1434
                     for member_uuid in
1435
                       self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1436

    
1437
  @locking.ssynchronized(_config_lock, shared=1)
1438
  def GetMultiNodeGroupInfo(self, group_uuids):
1439
    """Get the configuration of multiple node groups.
1440

1441
    @param group_uuids: List of node group UUIDs
1442
    @rtype: list
1443
    @return: List of tuples of (group_uuid, group_info)
1444

1445
    """
1446
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1447

    
1448
  @locking.ssynchronized(_config_lock)
1449
  def AddInstance(self, instance, ec_id):
1450
    """Add an instance to the config.
1451

1452
    This should be used after creating a new instance.
1453

1454
    @type instance: L{objects.Instance}
1455
    @param instance: the instance object
1456

1457
    """
1458
    if not isinstance(instance, objects.Instance):
1459
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1460

    
1461
    if instance.disk_template != constants.DT_DISKLESS:
1462
      all_lvs = instance.MapLVsByNode()
1463
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1464

    
1465
    all_macs = self._AllMACs()
1466
    for nic in instance.nics:
1467
      if nic.mac in all_macs:
1468
        raise errors.ConfigurationError("Cannot add instance %s:"
1469
                                        " MAC address '%s' already in use." %
1470
                                        (instance.name, nic.mac))
1471

    
1472
    self._CheckUniqueUUID(instance, include_temporary=False)
1473

    
1474
    instance.serial_no = 1
1475
    instance.ctime = instance.mtime = time.time()
1476
    self._config_data.instances[instance.uuid] = instance
1477
    self._config_data.cluster.serial_no += 1
1478
    self._UnlockedReleaseDRBDMinors(instance.uuid)
1479
    self._UnlockedCommitTemporaryIps(ec_id)
1480
    self._WriteConfig()
1481

    
1482
  def _EnsureUUID(self, item, ec_id):
1483
    """Ensures a given object has a valid UUID.
1484

1485
    @param item: the instance or node to be checked
1486
    @param ec_id: the execution context id for the uuid reservation
1487

1488
    """
1489
    if not item.uuid:
1490
      item.uuid = self._GenerateUniqueID(ec_id)
1491
    else:
1492
      self._CheckUniqueUUID(item, include_temporary=True)
1493

    
1494
  def _CheckUniqueUUID(self, item, include_temporary):
1495
    """Checks that the UUID of the given object is unique.
1496

1497
    @param item: the instance or node to be checked
1498
    @param include_temporary: whether temporarily generated UUID's should be
1499
              included in the check. If the UUID of the item to be checked is
1500
              a temporarily generated one, this has to be C{False}.
1501

1502
    """
1503
    if not item.uuid:
1504
      raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1505
    if item.uuid in self._AllIDs(include_temporary=include_temporary):
1506
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1507
                                      " in use" % (item.name, item.uuid))
1508

    
1509
  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1510
    """Set the instance's status to a given value.
1511

1512
    """
1513
    if inst_uuid not in self._config_data.instances:
1514
      raise errors.ConfigurationError("Unknown instance '%s'" %
1515
                                      inst_uuid)
1516
    instance = self._config_data.instances[inst_uuid]
1517

    
1518
    if status is None:
1519
      status = instance.admin_state
1520
    if disks_active is None:
1521
      disks_active = instance.disks_active
1522

    
1523
    assert status in constants.ADMINST_ALL, \
1524
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1525

    
1526
    if instance.admin_state != status or \
1527
       instance.disks_active != disks_active:
1528
      instance.admin_state = status
1529
      instance.disks_active = disks_active
1530
      instance.serial_no += 1
1531
      instance.mtime = time.time()
1532
      self._WriteConfig()
1533

    
1534
  @locking.ssynchronized(_config_lock)
1535
  def MarkInstanceUp(self, inst_uuid):
1536
    """Mark the instance status to up in the config.
1537

1538
    This also sets the instance disks active flag.
1539

1540
    """
1541
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1542

    
1543
  @locking.ssynchronized(_config_lock)
1544
  def MarkInstanceOffline(self, inst_uuid):
1545
    """Mark the instance status to down in the config.
1546

1547
    This also clears the instance disks active flag.
1548

1549
    """
1550
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1551

    
1552
  @locking.ssynchronized(_config_lock)
1553
  def RemoveInstance(self, inst_uuid):
1554
    """Remove the instance from the configuration.
1555

1556
    """
1557
    if inst_uuid not in self._config_data.instances:
1558
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1559

    
1560
    # If a network port has been allocated to the instance,
1561
    # return it to the pool of free ports.
1562
    inst = self._config_data.instances[inst_uuid]
1563
    network_port = getattr(inst, "network_port", None)
1564
    if network_port is not None:
1565
      self._config_data.cluster.tcpudp_port_pool.add(network_port)
1566

    
1567
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1568

    
1569
    for nic in instance.nics:
1570
      if nic.network and nic.ip:
1571
        # Return all IP addresses to the respective address pools
1572
        self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1573

    
1574
    del self._config_data.instances[inst_uuid]
1575
    self._config_data.cluster.serial_no += 1
1576
    self._WriteConfig()
1577

    
1578
  @locking.ssynchronized(_config_lock)
1579
  def RenameInstance(self, inst_uuid, new_name):
1580
    """Rename an instance.
1581

1582
    This needs to be done in ConfigWriter and not by RemoveInstance
1583
    combined with AddInstance as only we can guarantee an atomic
1584
    rename.
1585

1586
    """
1587
    if inst_uuid not in self._config_data.instances:
1588
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1589

    
1590
    inst = self._config_data.instances[inst_uuid]
1591
    inst.name = new_name
1592

    
1593
    for (_, disk) in enumerate(inst.disks):
1594
      if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1595
        # rename the file paths in logical and physical id
1596
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1597
        disk.logical_id = (disk.logical_id[0],
1598
                           utils.PathJoin(file_storage_dir, inst.name,
1599
                                          os.path.basename(disk.logical_id[1])))
1600

    
1601
    # Force update of ssconf files
1602
    self._config_data.cluster.serial_no += 1
1603

    
1604
    self._WriteConfig()
1605

    
1606
  @locking.ssynchronized(_config_lock)
1607
  def MarkInstanceDown(self, inst_uuid):
1608
    """Mark the status of an instance to down in the configuration.
1609

1610
    This does not touch the instance disks active flag, as shut down instances
1611
    can still have active disks.
1612

1613
    """
1614
    self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1615

    
1616
  @locking.ssynchronized(_config_lock)
1617
  def MarkInstanceDisksActive(self, inst_uuid):
1618
    """Mark the status of instance disks active.
1619

1620
    """
1621
    self._SetInstanceStatus(inst_uuid, None, True)
1622

    
1623
  @locking.ssynchronized(_config_lock)
1624
  def MarkInstanceDisksInactive(self, inst_uuid):
1625
    """Mark the status of instance disks inactive.
1626

1627
    """
1628
    self._SetInstanceStatus(inst_uuid, None, False)
1629

    
1630
  def _UnlockedGetInstanceList(self):
1631
    """Get the list of instances.
1632

1633
    This function is for internal use, when the config lock is already held.
1634

1635
    """
1636
    return self._config_data.instances.keys()
1637

    
1638
  @locking.ssynchronized(_config_lock, shared=1)
1639
  def GetInstanceList(self):
1640
    """Get the list of instances.
1641

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

1644
    """
1645
    return self._UnlockedGetInstanceList()
1646

    
1647
  def ExpandInstanceName(self, short_name):
1648
    """Attempt to expand an incomplete instance name.
1649

1650
    """
1651
    # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1652
    all_insts = self.GetAllInstancesInfo().values()
1653
    expanded_name = _MatchNameComponentIgnoreCase(
1654
                      short_name, [inst.name for inst in all_insts])
1655

    
1656
    if expanded_name is not None:
1657
      # there has to be exactly one instance with that name
1658
      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1659
      return (inst.uuid, inst.name)
1660
    else:
1661
      return (None, None)
1662

    
1663
  def _UnlockedGetInstanceInfo(self, inst_uuid):
1664
    """Returns information about an instance.
1665

1666
    This function is for internal use, when the config lock is already held.
1667

1668
    """
1669
    if inst_uuid not in self._config_data.instances:
1670
      return None
1671

    
1672
    return self._config_data.instances[inst_uuid]
1673

    
1674
  @locking.ssynchronized(_config_lock, shared=1)
1675
  def GetInstanceInfo(self, inst_uuid):
1676
    """Returns information about an instance.
1677

1678
    It takes the information from the configuration file. Other information of
1679
    an instance are taken from the live systems.
1680

1681
    @param inst_uuid: UUID of the instance
1682

1683
    @rtype: L{objects.Instance}
1684
    @return: the instance object
1685

1686
    """
1687
    return self._UnlockedGetInstanceInfo(inst_uuid)
1688

    
1689
  @locking.ssynchronized(_config_lock, shared=1)
1690
  def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1691
    """Returns set of node group UUIDs for instance's nodes.
1692

1693
    @rtype: frozenset
1694

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

    
1700
    if primary_only:
1701
      nodes = [instance.primary_node]
1702
    else:
1703
      nodes = instance.all_nodes
1704

    
1705
    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1706
                     for node_uuid in nodes)
1707

    
1708
  @locking.ssynchronized(_config_lock, shared=1)
1709
  def GetInstanceNetworks(self, inst_uuid):
1710
    """Returns set of network UUIDs for instance's nics.
1711

1712
    @rtype: frozenset
1713

1714
    """
1715
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1716
    if not instance:
1717
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1718

    
1719
    networks = set()
1720
    for nic in instance.nics:
1721
      if nic.network:
1722
        networks.add(nic.network)
1723

    
1724
    return frozenset(networks)
1725

    
1726
  @locking.ssynchronized(_config_lock, shared=1)
1727
  def GetMultiInstanceInfo(self, inst_uuids):
1728
    """Get the configuration of multiple instances.
1729

1730
    @param inst_uuids: list of instance UUIDs
1731
    @rtype: list
1732
    @return: list of tuples (instance UUID, instance_info), where
1733
        instance_info is what would GetInstanceInfo return for the
1734
        node, while keeping the original order
1735

1736
    """
1737
    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1738

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

1743
    @param inst_names: list of instance names
1744
    @rtype: list
1745
    @return: list of tuples (instance, instance_info), where
1746
        instance_info is what would GetInstanceInfo return for the
1747
        node, while keeping the original order
1748

1749
    """
1750
    result = []
1751
    for name in inst_names:
1752
      instance = self._UnlockedGetInstanceInfoByName(name)
1753
      result.append((instance.uuid, instance))
1754
    return result
1755

    
1756
  @locking.ssynchronized(_config_lock, shared=1)
1757
  def GetAllInstancesInfo(self):
1758
    """Get the configuration of all instances.
1759

1760
    @rtype: dict
1761
    @return: dict of (instance, instance_info), where instance_info is what
1762
              would GetInstanceInfo return for the node
1763

1764
    """
1765
    return self._UnlockedGetAllInstancesInfo()
1766

    
1767
  def _UnlockedGetAllInstancesInfo(self):
1768
    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1769
                    for inst_uuid in self._UnlockedGetInstanceList()])
1770
    return my_dict
1771

    
1772
  @locking.ssynchronized(_config_lock, shared=1)
1773
  def GetInstancesInfoByFilter(self, filter_fn):
1774
    """Get instance configuration with a filter.
1775

1776
    @type filter_fn: callable
1777
    @param filter_fn: Filter function receiving instance object as parameter,
1778
      returning boolean. Important: this function is called while the
1779
      configuration locks is held. It must not do any complex work or call
1780
      functions potentially leading to a deadlock. Ideally it doesn't call any
1781
      other functions and just compares instance attributes.
1782

1783
    """
1784
    return dict((uuid, inst)
1785
                for (uuid, inst) in self._config_data.instances.items()
1786
                if filter_fn(inst))
1787

    
1788
  @locking.ssynchronized(_config_lock, shared=1)
1789
  def GetInstanceInfoByName(self, inst_name):
1790
    """Get the L{objects.Instance} object for a named instance.
1791

1792
    @param inst_name: name of the instance to get information for
1793
    @type inst_name: string
1794
    @return: the corresponding L{objects.Instance} instance or None if no
1795
          information is available
1796

1797
    """
1798
    return self._UnlockedGetInstanceInfoByName(inst_name)
1799

    
1800
  def _UnlockedGetInstanceInfoByName(self, inst_name):
1801
    for inst in self._UnlockedGetAllInstancesInfo().values():
1802
      if inst.name == inst_name:
1803
        return inst
1804
    return None
1805

    
1806
  def _UnlockedGetInstanceName(self, inst_uuid):
1807
    inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1808
    if inst_info is None:
1809
      raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1810
    return inst_info.name
1811

    
1812
  @locking.ssynchronized(_config_lock, shared=1)
1813
  def GetInstanceName(self, inst_uuid):
1814
    """Gets the instance name for the passed instance.
1815

1816
    @param inst_uuid: instance UUID to get name for
1817
    @type inst_uuid: string
1818
    @rtype: string
1819
    @return: instance name
1820

1821
    """
1822
    return self._UnlockedGetInstanceName(inst_uuid)
1823

    
1824
  @locking.ssynchronized(_config_lock, shared=1)
1825
  def GetInstanceNames(self, inst_uuids):
1826
    """Gets the instance names for the passed list of nodes.
1827

1828
    @param inst_uuids: list of instance UUIDs to get names for
1829
    @type inst_uuids: list of strings
1830
    @rtype: list of strings
1831
    @return: list of instance names
1832

1833
    """
1834
    return self._UnlockedGetInstanceNames(inst_uuids)
1835

    
1836
  def _UnlockedGetInstanceNames(self, inst_uuids):
1837
    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1838

    
1839
  @locking.ssynchronized(_config_lock)
1840
  def AddNode(self, node, ec_id):
1841
    """Add a node to the configuration.
1842

1843
    @type node: L{objects.Node}
1844
    @param node: a Node instance
1845

1846
    """
1847
    logging.info("Adding node %s to configuration", node.name)
1848

    
1849
    self._EnsureUUID(node, ec_id)
1850

    
1851
    node.serial_no = 1
1852
    node.ctime = node.mtime = time.time()
1853
    self._UnlockedAddNodeToGroup(node.uuid, node.group)
1854
    self._config_data.nodes[node.uuid] = node
1855
    self._config_data.cluster.serial_no += 1
1856
    self._WriteConfig()
1857

    
1858
  @locking.ssynchronized(_config_lock)
1859
  def RemoveNode(self, node_uuid):
1860
    """Remove a node from the configuration.
1861

1862
    """
1863
    logging.info("Removing node %s from configuration", node_uuid)
1864

    
1865
    if node_uuid not in self._config_data.nodes:
1866
      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1867

    
1868
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1869
    del self._config_data.nodes[node_uuid]
1870
    self._config_data.cluster.serial_no += 1
1871
    self._WriteConfig()
1872

    
1873
  def ExpandNodeName(self, short_name):
1874
    """Attempt to expand an incomplete node name into a node UUID.
1875

1876
    """
1877
    # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1878
    all_nodes = self.GetAllNodesInfo().values()
1879
    expanded_name = _MatchNameComponentIgnoreCase(
1880
                      short_name, [node.name for node in all_nodes])
1881

    
1882
    if expanded_name is not None:
1883
      # there has to be exactly one node with that name
1884
      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1885
      return (node.uuid, node.name)
1886
    else:
1887
      return (None, None)
1888

    
1889
  def _UnlockedGetNodeInfo(self, node_uuid):
1890
    """Get the configuration of a node, as stored in the config.
1891

1892
    This function is for internal use, when the config lock is already
1893
    held.
1894

1895
    @param node_uuid: the node UUID
1896

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

1900
    """
1901
    if node_uuid not in self._config_data.nodes:
1902
      return None
1903

    
1904
    return self._config_data.nodes[node_uuid]
1905

    
1906
  @locking.ssynchronized(_config_lock, shared=1)
1907
  def GetNodeInfo(self, node_uuid):
1908
    """Get the configuration of a node, as stored in the config.
1909

1910
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1911

1912
    @param node_uuid: the node UUID
1913

1914
    @rtype: L{objects.Node}
1915
    @return: the node object
1916

1917
    """
1918
    return self._UnlockedGetNodeInfo(node_uuid)
1919

    
1920
  @locking.ssynchronized(_config_lock, shared=1)
1921
  def GetNodeInstances(self, node_uuid):
1922
    """Get the instances of a node, as stored in the config.
1923

1924
    @param node_uuid: the node UUID
1925

1926
    @rtype: (list, list)
1927
    @return: a tuple with two lists: the primary and the secondary instances
1928

1929
    """
1930
    pri = []
1931
    sec = []
1932
    for inst in self._config_data.instances.values():
1933
      if inst.primary_node == node_uuid:
1934
        pri.append(inst.uuid)
1935
      if node_uuid in inst.secondary_nodes:
1936
        sec.append(inst.uuid)
1937
    return (pri, sec)
1938

    
1939
  @locking.ssynchronized(_config_lock, shared=1)
1940
  def GetNodeGroupInstances(self, uuid, primary_only=False):
1941
    """Get the instances of a node group.
1942

1943
    @param uuid: Node group UUID
1944
    @param primary_only: Whether to only consider primary nodes
1945
    @rtype: frozenset
1946
    @return: List of instance UUIDs in node group
1947

1948
    """
1949
    if primary_only:
1950
      nodes_fn = lambda inst: [inst.primary_node]
1951
    else:
1952
      nodes_fn = lambda inst: inst.all_nodes
1953

    
1954
    return frozenset(inst.uuid
1955
                     for inst in self._config_data.instances.values()
1956
                     for node_uuid in nodes_fn(inst)
1957
                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1958

    
1959
  def _UnlockedGetHvparamsString(self, hvname):
1960
    """Return the string representation of the list of hyervisor parameters of
1961
    the given hypervisor.
1962

1963
    @see: C{GetHvparams}
1964

1965
    """
1966
    result = ""
1967
    hvparams = self._config_data.cluster.hvparams[hvname]
1968
    for key in hvparams:
1969
      result += "%s=%s\n" % (key, hvparams[key])
1970
    return result
1971

    
1972
  @locking.ssynchronized(_config_lock, shared=1)
1973
  def GetHvparamsString(self, hvname):
1974
    """Return the hypervisor parameters of the given hypervisor.
1975

1976
    @type hvname: string
1977
    @param hvname: name of a hypervisor
1978
    @rtype: string
1979
    @return: string containing key-value-pairs, one pair on each line;
1980
      format: KEY=VALUE
1981

1982
    """
1983
    return self._UnlockedGetHvparamsString(hvname)
1984

    
1985
  def _UnlockedGetNodeList(self):
1986
    """Return the list of nodes which are in the configuration.
1987

1988
    This function is for internal use, when the config lock is already
1989
    held.
1990

1991
    @rtype: list
1992

1993
    """
1994
    return self._config_data.nodes.keys()
1995

    
1996
  @locking.ssynchronized(_config_lock, shared=1)
1997
  def GetNodeList(self):
1998
    """Return the list of nodes which are in the configuration.
1999

2000
    """
2001
    return self._UnlockedGetNodeList()
2002

    
2003
  def _UnlockedGetOnlineNodeList(self):
2004
    """Return the list of nodes which are online.
2005

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

    
2011
  @locking.ssynchronized(_config_lock, shared=1)
2012
  def GetOnlineNodeList(self):
2013
    """Return the list of nodes which are online.
2014

2015
    """
2016
    return self._UnlockedGetOnlineNodeList()
2017

    
2018
  @locking.ssynchronized(_config_lock, shared=1)
2019
  def GetVmCapableNodeList(self):
2020
    """Return the list of nodes which are not vm capable.
2021

2022
    """
2023
    all_nodes = [self._UnlockedGetNodeInfo(node)
2024
                 for node in self._UnlockedGetNodeList()]
2025
    return [node.uuid for node in all_nodes if node.vm_capable]
2026

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

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

    
2036
  @locking.ssynchronized(_config_lock, shared=1)
2037
  def GetMultiNodeInfo(self, node_uuids):
2038
    """Get the configuration of multiple nodes.
2039

2040
    @param node_uuids: list of node UUIDs
2041
    @rtype: list
2042
    @return: list of tuples of (node, node_info), where node_info is
2043
        what would GetNodeInfo return for the node, in the original
2044
        order
2045

2046
    """
2047
    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
2048

    
2049
  def _UnlockedGetAllNodesInfo(self):
2050
    """Gets configuration of all nodes.
2051

2052
    @note: See L{GetAllNodesInfo}
2053

2054
    """
2055
    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
2056
                 for node_uuid in self._UnlockedGetNodeList()])
2057

    
2058
  @locking.ssynchronized(_config_lock, shared=1)
2059
  def GetAllNodesInfo(self):
2060
    """Get the configuration of all nodes.
2061

2062
    @rtype: dict
2063
    @return: dict of (node, node_info), where node_info is what
2064
              would GetNodeInfo return for the node
2065

2066
    """
2067
    return self._UnlockedGetAllNodesInfo()
2068

    
2069
  def _UnlockedGetNodeInfoByName(self, node_name):
2070
    for node in self._UnlockedGetAllNodesInfo().values():
2071
      if node.name == node_name:
2072
        return node
2073
    return None
2074

    
2075
  @locking.ssynchronized(_config_lock, shared=1)
2076
  def GetNodeInfoByName(self, node_name):
2077
    """Get the L{objects.Node} object for a named node.
2078

2079
    @param node_name: name of the node to get information for
2080
    @type node_name: string
2081
    @return: the corresponding L{objects.Node} instance or None if no
2082
          information is available
2083

2084
    """
2085
    return self._UnlockedGetNodeInfoByName(node_name)
2086

    
2087
  @locking.ssynchronized(_config_lock, shared=1)
2088
  def GetNodeGroupInfoByName(self, nodegroup_name):
2089
    """Get the L{objects.NodeGroup} object for a named node group.
2090

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

2096
    """
2097
    for nodegroup in self._UnlockedGetAllNodeGroupsInfo().values():
2098
      if nodegroup.name == nodegroup_name:
2099
        return nodegroup
2100
    return None
2101

    
2102
  def _UnlockedGetNodeName(self, node_spec):
2103
    if isinstance(node_spec, objects.Node):
2104
      return node_spec.name
2105
    elif isinstance(node_spec, basestring):
2106
      node_info = self._UnlockedGetNodeInfo(node_spec)
2107
      if node_info is None:
2108
        raise errors.OpExecError("Unknown node: %s" % node_spec)
2109
      return node_info.name
2110
    else:
2111
      raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2112

    
2113
  @locking.ssynchronized(_config_lock, shared=1)
2114
  def GetNodeName(self, node_spec):
2115
    """Gets the node name for the passed node.
2116

2117
    @param node_spec: node to get names for
2118
    @type node_spec: either node UUID or a L{objects.Node} object
2119
    @rtype: string
2120
    @return: node name
2121

2122
    """
2123
    return self._UnlockedGetNodeName(node_spec)
2124

    
2125
  def _UnlockedGetNodeNames(self, node_specs):
2126
    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2127

    
2128
  @locking.ssynchronized(_config_lock, shared=1)
2129
  def GetNodeNames(self, node_specs):
2130
    """Gets the node names for the passed list of nodes.
2131

2132
    @param node_specs: list of nodes to get names for
2133
    @type node_specs: list of either node UUIDs or L{objects.Node} objects
2134
    @rtype: list of strings
2135
    @return: list of node names
2136

2137
    """
2138
    return self._UnlockedGetNodeNames(node_specs)
2139

    
2140
  @locking.ssynchronized(_config_lock, shared=1)
2141
  def GetNodeGroupsFromNodes(self, node_uuids):
2142
    """Returns groups for a list of nodes.
2143

2144
    @type node_uuids: list of string
2145
    @param node_uuids: List of node UUIDs
2146
    @rtype: frozenset
2147

2148
    """
2149
    return frozenset(self._UnlockedGetNodeInfo(uuid).group
2150
                     for uuid in node_uuids)
2151

    
2152
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2153
    """Get the number of current and maximum desired and possible candidates.
2154

2155
    @type exceptions: list
2156
    @param exceptions: if passed, list of nodes that should be ignored
2157
    @rtype: tuple
2158
    @return: tuple of (current, desired and possible, possible)
2159

2160
    """
2161
    mc_now = mc_should = mc_max = 0
2162
    for node in self._config_data.nodes.values():
2163
      if exceptions and node.uuid in exceptions:
2164
        continue
2165
      if not (node.offline or node.drained) and node.master_capable:
2166
        mc_max += 1
2167
      if node.master_candidate:
2168
        mc_now += 1
2169
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2170
    return (mc_now, mc_should, mc_max)
2171

    
2172
  @locking.ssynchronized(_config_lock, shared=1)
2173
  def GetMasterCandidateStats(self, exceptions=None):
2174
    """Get the number of current and maximum possible candidates.
2175

2176
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2177

2178
    @type exceptions: list
2179
    @param exceptions: if passed, list of nodes that should be ignored
2180
    @rtype: tuple
2181
    @return: tuple of (current, max)
2182

2183
    """
2184
    return self._UnlockedGetMasterCandidateStats(exceptions)
2185

    
2186
  @locking.ssynchronized(_config_lock)
2187
  def MaintainCandidatePool(self, exception_node_uuids):
2188
    """Try to grow the candidate pool to the desired size.
2189

2190
    @type exception_node_uuids: list
2191
    @param exception_node_uuids: if passed, list of nodes that should be ignored
2192
    @rtype: list
2193
    @return: list with the adjusted nodes (L{objects.Node} instances)
2194

2195
    """
2196
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2197
                          exception_node_uuids)
2198
    mod_list = []
2199
    if mc_now < mc_max:
2200
      node_list = self._config_data.nodes.keys()
2201
      random.shuffle(node_list)
2202
      for uuid in node_list:
2203
        if mc_now >= mc_max:
2204
          break
2205
        node = self._config_data.nodes[uuid]
2206
        if (node.master_candidate or node.offline or node.drained or
2207
            node.uuid in exception_node_uuids or not node.master_capable):
2208
          continue
2209
        mod_list.append(node)
2210
        node.master_candidate = True
2211
        node.serial_no += 1
2212
        mc_now += 1
2213
      if mc_now != mc_max:
2214
        # this should not happen
2215
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
2216
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
2217
      if mod_list:
2218
        self._config_data.cluster.serial_no += 1
2219
        self._WriteConfig()
2220

    
2221
    return mod_list
2222

    
2223
  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2224
    """Add a given node to the specified group.
2225

2226
    """
2227
    if nodegroup_uuid not in self._config_data.nodegroups:
2228
      # This can happen if a node group gets deleted between its lookup and
2229
      # when we're adding the first node to it, since we don't keep a lock in
2230
      # the meantime. It's ok though, as we'll fail cleanly if the node group
2231
      # is not found anymore.
2232
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2233
    if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2234
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2235

    
2236
  def _UnlockedRemoveNodeFromGroup(self, node):
2237
    """Remove a given node from its group.
2238

2239
    """
2240
    nodegroup = node.group
2241
    if nodegroup not in self._config_data.nodegroups:
2242
      logging.warning("Warning: node '%s' has unknown node group '%s'"
2243
                      " (while being removed from it)", node.uuid, nodegroup)
2244
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
2245
    if node.uuid not in nodegroup_obj.members:
2246
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
2247
                      " (while being removed from it)", node.uuid, nodegroup)
2248
    else:
2249
      nodegroup_obj.members.remove(node.uuid)
2250

    
2251
  @locking.ssynchronized(_config_lock)
2252
  def AssignGroupNodes(self, mods):
2253
    """Changes the group of a number of nodes.
2254

2255
    @type mods: list of tuples; (node name, new group UUID)
2256
    @param mods: Node membership modifications
2257

2258
    """
2259
    groups = self._config_data.nodegroups
2260
    nodes = self._config_data.nodes
2261

    
2262
    resmod = []
2263

    
2264
    # Try to resolve UUIDs first
2265
    for (node_uuid, new_group_uuid) in mods:
2266
      try:
2267
        node = nodes[node_uuid]
2268
      except KeyError:
2269
        raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2270

    
2271
      if node.group == new_group_uuid:
2272
        # Node is being assigned to its current group
2273
        logging.debug("Node '%s' was assigned to its current group (%s)",
2274
                      node_uuid, node.group)
2275
        continue
2276

    
2277
      # Try to find current group of node
2278
      try:
2279
        old_group = groups[node.group]
2280
      except KeyError:
2281
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2282
                                        node.group)
2283

    
2284
      # Try to find new group for node
2285
      try:
2286
        new_group = groups[new_group_uuid]
2287
      except KeyError:
2288
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2289
                                        new_group_uuid)
2290

    
2291
      assert node.uuid in old_group.members, \
2292
        ("Inconsistent configuration: node '%s' not listed in members for its"
2293
         " old group '%s'" % (node.uuid, old_group.uuid))
2294
      assert node.uuid not in new_group.members, \
2295
        ("Inconsistent configuration: node '%s' already listed in members for"
2296
         " its new group '%s'" % (node.uuid, new_group.uuid))
2297

    
2298
      resmod.append((node, old_group, new_group))
2299

    
2300
    # Apply changes
2301
    for (node, old_group, new_group) in resmod:
2302
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2303
        "Assigning to current group is not possible"
2304

    
2305
      node.group = new_group.uuid
2306

    
2307
      # Update members of involved groups
2308
      if node.uuid in old_group.members:
2309
        old_group.members.remove(node.uuid)
2310
      if node.uuid not in new_group.members:
2311
        new_group.members.append(node.uuid)
2312

    
2313
    # Update timestamps and serials (only once per node/group object)
2314
    now = time.time()
2315
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2316
      obj.serial_no += 1
2317
      obj.mtime = now
2318

    
2319
    # Force ssconf update
2320
    self._config_data.cluster.serial_no += 1
2321

    
2322
    self._WriteConfig()
2323

    
2324
  def _BumpSerialNo(self):
2325
    """Bump up the serial number of the config.
2326

2327
    """
2328
    self._config_data.serial_no += 1
2329
    self._config_data.mtime = time.time()
2330

    
2331
  def _AllUUIDObjects(self):
2332
    """Returns all objects with uuid attributes.
2333

2334
    """
2335
    return (self._config_data.instances.values() +
2336
            self._config_data.nodes.values() +
2337
            self._config_data.nodegroups.values() +
2338
            self._config_data.networks.values() +
2339
            self._AllDisks() +
2340
            self._AllNICs() +
2341
            [self._config_data.cluster])
2342

    
2343
  def _OpenConfig(self, accept_foreign):
2344
    """Read the config data from disk.
2345

2346
    """
2347
    raw_data = utils.ReadFile(self._cfg_file)
2348

    
2349
    try:
2350
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2351
    except Exception, err:
2352
      raise errors.ConfigurationError(err)
2353

    
2354
    # Make sure the configuration has the right version
2355
    _ValidateConfig(data)
2356

    
2357
    if (not hasattr(data, "cluster") or
2358
        not hasattr(data.cluster, "rsahostkeypub")):
2359
      raise errors.ConfigurationError("Incomplete configuration"
2360
                                      " (missing cluster.rsahostkeypub)")
2361

    
2362
    if not data.cluster.master_node in data.nodes:
2363
      msg = ("The configuration denotes node %s as master, but does not"
2364
             " contain information about this node" %
2365
             data.cluster.master_node)
2366
      raise errors.ConfigurationError(msg)
2367

    
2368
    master_info = data.nodes[data.cluster.master_node]
2369
    if master_info.name != self._my_hostname and not accept_foreign:
2370
      msg = ("The configuration denotes node %s as master, while my"
2371
             " hostname is %s; opening a foreign configuration is only"
2372
             " possible in accept_foreign mode" %
2373
             (master_info.name, self._my_hostname))
2374
      raise errors.ConfigurationError(msg)
2375

    
2376
    self._config_data = data
2377
    # reset the last serial as -1 so that the next write will cause
2378
    # ssconf update
2379
    self._last_cluster_serial = -1
2380

    
2381
    # Upgrade configuration if needed
2382
    self._UpgradeConfig()
2383

    
2384
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2385

    
2386
  def _UpgradeConfig(self):
2387
    """Run any upgrade steps.
2388

2389
    This method performs both in-object upgrades and also update some data
2390
    elements that need uniqueness across the whole configuration or interact
2391
    with other objects.
2392

2393
    @warning: this function will call L{_WriteConfig()}, but also
2394
        L{DropECReservations} so it needs to be called only from a
2395
        "safe" place (the constructor). If one wanted to call it with
2396
        the lock held, a DropECReservationUnlocked would need to be
2397
        created first, to avoid causing deadlock.
2398

2399
    """
2400
    # Keep a copy of the persistent part of _config_data to check for changes
2401
    # Serialization doesn't guarantee order in dictionaries
2402
    oldconf = copy.deepcopy(self._config_data.ToDict())
2403

    
2404
    # In-object upgrades
2405
    self._config_data.UpgradeConfig()
2406

    
2407
    for item in self._AllUUIDObjects():
2408
      if item.uuid is None:
2409
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2410
    if not self._config_data.nodegroups:
2411
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2412
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2413
                                            members=[])
2414
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2415
    for node in self._config_data.nodes.values():
2416
      if not node.group:
2417
        node.group = self.LookupNodeGroup(None)
2418
      # This is technically *not* an upgrade, but needs to be done both when
2419
      # nodegroups are being added, and upon normally loading the config,
2420
      # because the members list of a node group is discarded upon
2421
      # serializing/deserializing the object.
2422
      self._UnlockedAddNodeToGroup(node.uuid, node.group)
2423

    
2424
    modified = (oldconf != self._config_data.ToDict())
2425
    if modified:
2426
      self._WriteConfig()
2427
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2428
      # only called at config init time, without the lock held
2429
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2430
    else:
2431
      config_errors = self._UnlockedVerifyConfig()
2432
      if config_errors:
2433
        errmsg = ("Loaded configuration data is not consistent: %s" %
2434
                  (utils.CommaJoin(config_errors)))
2435
        logging.critical(errmsg)
2436

    
2437
  def _DistributeConfig(self, feedback_fn):
2438
    """Distribute the configuration to the other nodes.
2439

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

2443
    """
2444
    if self._offline:
2445
      return True
2446

    
2447
    bad = False
2448

    
2449
    node_list = []
2450
    addr_list = []
2451
    myhostname = self._my_hostname
2452
    # we can skip checking whether _UnlockedGetNodeInfo returns None
2453
    # since the node list comes from _UnlocketGetNodeList, and we are
2454
    # called with the lock held, so no modifications should take place
2455
    # in between
2456
    for node_uuid in self._UnlockedGetNodeList():
2457
      node_info = self._UnlockedGetNodeInfo(node_uuid)
2458
      if node_info.name == myhostname or not node_info.master_candidate:
2459
        continue
2460
      node_list.append(node_info.name)
2461
      addr_list.append(node_info.primary_ip)
2462

    
2463
    # TODO: Use dedicated resolver talking to config writer for name resolution
2464
    result = \
2465
      self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2466
    for to_node, to_result in result.items():
2467
      msg = to_result.fail_msg
2468
      if msg:
2469
        msg = ("Copy of file %s to node %s failed: %s" %
2470
               (self._cfg_file, to_node, msg))
2471
        logging.error(msg)
2472

    
2473
        if feedback_fn:
2474
          feedback_fn(msg)
2475

    
2476
        bad = True
2477

    
2478
    return not bad
2479

    
2480
  def _WriteConfig(self, destination=None, feedback_fn=None):
2481
    """Write the configuration data to persistent storage.
2482

2483
    """
2484
    assert feedback_fn is None or callable(feedback_fn)
2485

    
2486
    # Warn on config errors, but don't abort the save - the
2487
    # configuration has already been modified, and we can't revert;
2488
    # the best we can do is to warn the user and save as is, leaving
2489
    # recovery to the user
2490
    config_errors = self._UnlockedVerifyConfig()
2491
    if config_errors:
2492
      errmsg = ("Configuration data is not consistent: %s" %
2493
                (utils.CommaJoin(config_errors)))
2494
      logging.critical(errmsg)
2495
      if feedback_fn:
2496
        feedback_fn(errmsg)
2497

    
2498
    if destination is None:
2499
      destination = self._cfg_file
2500
    self._BumpSerialNo()
2501
    txt = serializer.Dump(self._config_data.ToDict())
2502

    
2503
    getents = self._getents()
2504
    try:
2505
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2506
                               close=False, gid=getents.confd_gid, mode=0640)
2507
    except errors.LockError:
2508
      raise errors.ConfigurationError("The configuration file has been"
2509
                                      " modified since the last write, cannot"
2510
                                      " update")
2511
    try:
2512
      self._cfg_id = utils.GetFileID(fd=fd)
2513
    finally:
2514
      os.close(fd)
2515

    
2516
    self.write_count += 1
2517

    
2518
    # and redistribute the config file to master candidates
2519
    self._DistributeConfig(feedback_fn)
2520

    
2521
    # Write ssconf files on all nodes (including locally)
2522
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
2523
      if not self._offline:
2524
        result = self._GetRpc(None).call_write_ssconf_files(
2525
          self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2526
          self._UnlockedGetSsconfValues())
2527

    
2528
        for nname, nresu in result.items():
2529
          msg = nresu.fail_msg
2530
          if msg:
2531
            errmsg = ("Error while uploading ssconf files to"
2532
                      " node %s: %s" % (nname, msg))
2533
            logging.warning(errmsg)
2534

    
2535
            if feedback_fn:
2536
              feedback_fn(errmsg)
2537

    
2538
      self._last_cluster_serial = self._config_data.cluster.serial_no
2539

    
2540
  def _GetAllHvparamsStrings(self, hypervisors):
2541
    """Get the hvparams of all given hypervisors from the config.
2542

2543
    @type hypervisors: list of string
2544
    @param hypervisors: list of hypervisor names
2545
    @rtype: dict of strings
2546
    @returns: dictionary mapping the hypervisor name to a string representation
2547
      of the hypervisor's hvparams
2548

2549
    """
2550
    hvparams = {}
2551
    for hv in hypervisors:
2552
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2553
    return hvparams
2554

    
2555
  @staticmethod
2556
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2557
    """Extends the ssconf_values dictionary by hvparams.
2558

2559
    @type ssconf_values: dict of strings
2560
    @param ssconf_values: dictionary mapping ssconf_keys to strings
2561
      representing the content of ssconf files
2562
    @type all_hvparams: dict of strings
2563
    @param all_hvparams: dictionary mapping hypervisor names to a string
2564
      representation of their hvparams
2565
    @rtype: same as ssconf_values
2566
    @returns: the ssconf_values dictionary extended by hvparams
2567

2568
    """
2569
    for hv in all_hvparams:
2570
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2571
      ssconf_values[ssconf_key] = all_hvparams[hv]
2572
    return ssconf_values
2573

    
2574
  def _UnlockedGetSsconfValues(self):
2575
    """Return the values needed by ssconf.
2576

2577
    @rtype: dict
2578
    @return: a dictionary with keys the ssconf names and values their
2579
        associated value
2580

2581
    """
2582
    fn = "\n".join
2583
    instance_names = utils.NiceSort(
2584
                       [inst.name for inst in
2585
                        self._UnlockedGetAllInstancesInfo().values()])
2586
    node_infos = self._UnlockedGetAllNodesInfo().values()
2587
    node_names = [node.name for node in node_infos]
2588
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2589
                    for ninfo in node_infos]
2590
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2591
                    for ninfo in node_infos]
2592

    
2593
    instance_data = fn(instance_names)
2594
    off_data = fn(node.name for node in node_infos if node.offline)
2595
    on_data = fn(node.name for node in node_infos if not node.offline)
2596
    mc_data = fn(node.name for node in node_infos if node.master_candidate)
2597
    mc_ips_data = fn(node.primary_ip for node in node_infos
2598
                     if node.master_candidate)
2599
    node_data = fn(node_names)
2600
    node_pri_ips_data = fn(node_pri_ips)
2601
    node_snd_ips_data = fn(node_snd_ips)
2602

    
2603
    cluster = self._config_data.cluster
2604
    cluster_tags = fn(cluster.GetTags())
2605

    
2606
    master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert)
2607
                                 for mc_uuid, mc_cert
2608
                                 in cluster.candidate_certs.items())
2609

    
2610
    hypervisor_list = fn(cluster.enabled_hypervisors)
2611
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2612

    
2613
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2614

    
2615
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2616
                  self._config_data.nodegroups.values()]
2617
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2618
    networks = ["%s %s" % (net.uuid, net.name) for net in
2619
                self._config_data.networks.values()]
2620
    networks_data = fn(utils.NiceSort(networks))
2621

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

    
2659
  @locking.ssynchronized(_config_lock, shared=1)
2660
  def GetSsconfValues(self):
2661
    """Wrapper using lock around _UnlockedGetSsconf().
2662

2663
    """
2664
    return self._UnlockedGetSsconfValues()
2665

    
2666
  @locking.ssynchronized(_config_lock, shared=1)
2667
  def GetVGName(self):
2668
    """Return the volume group name.
2669

2670
    """
2671
    return self._config_data.cluster.volume_group_name
2672

    
2673
  @locking.ssynchronized(_config_lock)
2674
  def SetVGName(self, vg_name):
2675
    """Set the volume group name.
2676

2677
    """
2678
    self._config_data.cluster.volume_group_name = vg_name
2679
    self._config_data.cluster.serial_no += 1
2680
    self._WriteConfig()
2681

    
2682
  @locking.ssynchronized(_config_lock, shared=1)
2683
  def GetDRBDHelper(self):
2684
    """Return DRBD usermode helper.
2685

2686
    """
2687
    return self._config_data.cluster.drbd_usermode_helper
2688

    
2689
  @locking.ssynchronized(_config_lock)
2690
  def SetDRBDHelper(self, drbd_helper):
2691
    """Set DRBD usermode helper.
2692

2693
    """
2694
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2695
    self._config_data.cluster.serial_no += 1
2696
    self._WriteConfig()
2697

    
2698
  @locking.ssynchronized(_config_lock, shared=1)
2699
  def GetMACPrefix(self):
2700
    """Return the mac prefix.
2701

2702
    """
2703
    return self._config_data.cluster.mac_prefix
2704

    
2705
  @locking.ssynchronized(_config_lock, shared=1)
2706
  def GetClusterInfo(self):
2707
    """Returns information about the cluster
2708

2709
    @rtype: L{objects.Cluster}
2710
    @return: the cluster object
2711

2712
    """
2713
    return self._config_data.cluster
2714

    
2715
  @locking.ssynchronized(_config_lock, shared=1)
2716
  def HasAnyDiskOfType(self, dev_type):
2717
    """Check if in there is at disk of the given type in the configuration.
2718

2719
    """
2720
    return self._config_data.HasAnyDiskOfType(dev_type)
2721

    
2722
  @locking.ssynchronized(_config_lock)
2723
  def Update(self, target, feedback_fn, ec_id=None):
2724
    """Notify function to be called after updates.
2725

2726
    This function must be called when an object (as returned by
2727
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2728
    caller wants the modifications saved to the backing store. Note
2729
    that all modified objects will be saved, but the target argument
2730
    is the one the caller wants to ensure that it's saved.
2731

2732
    @param target: an instance of either L{objects.Cluster},
2733
        L{objects.Node} or L{objects.Instance} which is existing in
2734
        the cluster
2735
    @param feedback_fn: Callable feedback function
2736

2737
    """
2738
    if self._config_data is None:
2739
      raise errors.ProgrammerError("Configuration file not read,"
2740
                                   " cannot save.")
2741
    update_serial = False
2742
    if isinstance(target, objects.Cluster):
2743
      test = target == self._config_data.cluster
2744
    elif isinstance(target, objects.Node):
2745
      test = target in self._config_data.nodes.values()
2746
      update_serial = True
2747
    elif isinstance(target, objects.Instance):
2748
      test = target in self._config_data.instances.values()
2749
    elif isinstance(target, objects.NodeGroup):
2750
      test = target in self._config_data.nodegroups.values()
2751
    elif isinstance(target, objects.Network):
2752
      test = target in self._config_data.networks.values()
2753
    else:
2754
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2755
                                   " ConfigWriter.Update" % type(target))
2756
    if not test:
2757
      raise errors.ConfigurationError("Configuration updated since object"
2758
                                      " has been read or unknown object")
2759
    target.serial_no += 1
2760
    target.mtime = now = time.time()
2761

    
2762
    if update_serial:
2763
      # for node updates, we need to increase the cluster serial too
2764
      self._config_data.cluster.serial_no += 1
2765
      self._config_data.cluster.mtime = now
2766

    
2767
    if isinstance(target, objects.Instance):
2768
      self._UnlockedReleaseDRBDMinors(target.uuid)
2769

    
2770
    if ec_id is not None:
2771
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2772
      self._UnlockedCommitTemporaryIps(ec_id)
2773

    
2774
    self._WriteConfig(feedback_fn=feedback_fn)
2775

    
2776
  @locking.ssynchronized(_config_lock)
2777
  def DropECReservations(self, ec_id):
2778
    """Drop per-execution-context reservations
2779

2780
    """
2781
    for rm in self._all_rms:
2782
      rm.DropECReservations(ec_id)
2783

    
2784
  @locking.ssynchronized(_config_lock, shared=1)
2785
  def GetAllNetworksInfo(self):
2786
    """Get configuration info of all the networks.
2787

2788
    """
2789
    return dict(self._config_data.networks)
2790

    
2791
  def _UnlockedGetNetworkList(self):
2792
    """Get the list of networks.
2793

2794
    This function is for internal use, when the config lock is already held.
2795

2796
    """
2797
    return self._config_data.networks.keys()
2798

    
2799
  @locking.ssynchronized(_config_lock, shared=1)
2800
  def GetNetworkList(self):
2801
    """Get the list of networks.
2802

2803
    @return: array of networks, ex. ["main", "vlan100", "200]
2804

2805
    """
2806
    return self._UnlockedGetNetworkList()
2807

    
2808
  @locking.ssynchronized(_config_lock, shared=1)
2809
  def GetNetworkNames(self):
2810
    """Get a list of network names
2811

2812
    """
2813
    names = [net.name
2814
             for net in self._config_data.networks.values()]
2815
    return names
2816

    
2817
  def _UnlockedGetNetwork(self, uuid):
2818
    """Returns information about a network.
2819

2820
    This function is for internal use, when the config lock is already held.
2821

2822
    """
2823
    if uuid not in self._config_data.networks:
2824
      return None
2825

    
2826
    return self._config_data.networks[uuid]
2827

    
2828
  @locking.ssynchronized(_config_lock, shared=1)
2829
  def GetNetwork(self, uuid):
2830
    """Returns information about a network.
2831

2832
    It takes the information from the configuration file.
2833

2834
    @param uuid: UUID of the network
2835

2836
    @rtype: L{objects.Network}
2837
    @return: the network object
2838

2839
    """
2840
    return self._UnlockedGetNetwork(uuid)
2841

    
2842
  @locking.ssynchronized(_config_lock)
2843
  def AddNetwork(self, net, ec_id, check_uuid=True):
2844
    """Add a network to the configuration.
2845

2846
    @type net: L{objects.Network}
2847
    @param net: the Network object to add
2848
    @type ec_id: string
2849
    @param ec_id: unique id for the job to use when creating a missing UUID
2850

2851
    """
2852
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2853
    self._WriteConfig()
2854

    
2855
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2856
    """Add a network to the configuration.
2857

2858
    """
2859
    logging.info("Adding network %s to configuration", net.name)
2860

    
2861
    if check_uuid:
2862
      self._EnsureUUID(net, ec_id)
2863

    
2864
    net.serial_no = 1
2865
    net.ctime = net.mtime = time.time()
2866
    self._config_data.networks[net.uuid] = net
2867
    self._config_data.cluster.serial_no += 1
2868

    
2869
  def _UnlockedLookupNetwork(self, target):
2870
    """Lookup a network's UUID.
2871

2872
    @type target: string
2873
    @param target: network name or UUID
2874
    @rtype: string
2875
    @return: network UUID
2876
    @raises errors.OpPrereqError: when the target network cannot be found
2877

2878
    """
2879
    if target is None:
2880
      return None
2881
    if target in self._config_data.networks:
2882
      return target
2883
    for net in self._config_data.networks.values():
2884
      if net.name == target:
2885
        return net.uuid
2886
    raise errors.OpPrereqError("Network '%s' not found" % target,
2887
                               errors.ECODE_NOENT)
2888

    
2889
  @locking.ssynchronized(_config_lock, shared=1)
2890
  def LookupNetwork(self, target):
2891
    """Lookup a network's UUID.
2892

2893
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2894

2895
    @type target: string
2896
    @param target: network name or UUID
2897
    @rtype: string
2898
    @return: network UUID
2899

2900
    """
2901
    return self._UnlockedLookupNetwork(target)
2902

    
2903
  @locking.ssynchronized(_config_lock)
2904
  def RemoveNetwork(self, network_uuid):
2905
    """Remove a network from the configuration.
2906

2907
    @type network_uuid: string
2908
    @param network_uuid: the UUID of the network to remove
2909

2910
    """
2911
    logging.info("Removing network %s from configuration", network_uuid)
2912

    
2913
    if network_uuid not in self._config_data.networks:
2914
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2915

    
2916
    del self._config_data.networks[network_uuid]
2917
    self._config_data.cluster.serial_no += 1
2918
    self._WriteConfig()
2919

    
2920
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2921
    """Get the netparams (mode, link) of a network.
2922

2923
    Get a network's netparams for a given node.
2924

2925
    @type net_uuid: string
2926
    @param net_uuid: network uuid
2927
    @type node_uuid: string
2928
    @param node_uuid: node UUID
2929
    @rtype: dict or None
2930
    @return: netparams
2931

2932
    """
2933
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2934
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2935
    netparams = nodegroup_info.networks.get(net_uuid, None)
2936

    
2937
    return netparams
2938

    
2939
  @locking.ssynchronized(_config_lock, shared=1)
2940
  def GetGroupNetParams(self, net_uuid, node_uuid):
2941
    """Locking wrapper of _UnlockedGetGroupNetParams()
2942

2943
    """
2944
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2945

    
2946
  @locking.ssynchronized(_config_lock, shared=1)
2947
  def CheckIPInNodeGroup(self, ip, node_uuid):
2948
    """Check IP uniqueness in nodegroup.
2949

2950
    Check networks that are connected in the node's node group
2951
    if ip is contained in any of them. Used when creating/adding
2952
    a NIC to ensure uniqueness among nodegroups.
2953

2954
    @type ip: string
2955
    @param ip: ip address
2956
    @type node_uuid: string
2957
    @param node_uuid: node UUID
2958
    @rtype: (string, dict) or (None, None)
2959
    @return: (network name, netparams)
2960

2961
    """
2962
    if ip is None:
2963
      return (None, None)
2964
    node_info = self._UnlockedGetNodeInfo(node_uuid)
2965
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2966
    for net_uuid in nodegroup_info.networks.keys():
2967
      net_info = self._UnlockedGetNetwork(net_uuid)
2968
      pool = network.AddressPool(net_info)
2969
      if pool.Contains(ip):
2970
        return (net_info.name, nodegroup_info.networks[net_uuid])
2971

    
2972
    return (None, None)