Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ ef534188

History | View | Annotate | Download (102.6 kB)

1
#
2
#
3

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

    
21

    
22
"""Configuration management for Ganeti
23

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

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

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

32
"""
33

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

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

    
45
from ganeti import errors
46
from ganeti import utils
47
from ganeti import constants
48
import ganeti.wconfd as wc
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
def GetWConfdContext(ec_id, livelock):
59
  """Prepare a context for communication with WConfd.
60

61
  WConfd needs to know the identity of each caller to properly manage locks and
62
  detect job death. This helper function prepares the identity object given a
63
  job ID (optional) and a livelock file.
64

65
  @type ec_id: int, or None
66
  @param ec_id: the job ID or None, if the caller isn't a job
67
  @type livelock: L{ganeti.utils.livelock.LiveLock}
68
  @param livelock: a livelock object holding the lockfile needed for WConfd
69
  @return: the WConfd context
70

71
  """
72
  if ec_id is None:
73
    return (threading.current_thread().getName(),
74
            livelock.lockfile.name)
75
  else:
76
    return (ec_id,
77
            livelock.lockfile.name)
78

    
79

    
80
def GetConfig(ec_id, livelock, **kwargs):
81
  """A utility function for constructing instances of ConfigWriter.
82

83
  It prepares a WConfd context and uses it to create a ConfigWriter instance.
84

85
  @type ec_id: int, or None
86
  @param ec_id: the job ID or None, if the caller isn't a job
87
  @type livelock: L{ganeti.utils.livelock.LiveLock}
88
  @param livelock: a livelock object holding the lockfile needed for WConfd
89
  @type kwargs: dict
90
  @param kwargs: Any additional arguments for the ConfigWriter constructor
91
  @rtype: L{ConfigWriter}
92
  @return: the ConfigWriter context
93

94
  """
95
  kwargs['wconfdcontext'] = GetWConfdContext(ec_id, livelock)
96
  kwargs['wconfd'] = wc.Client()
97
  return ConfigWriter(**kwargs)
98

    
99

    
100
def _ConfigSync(shared=0):
101
  """Configuration synchronization decorator.
102

103
  """
104
  def wrap(fn):
105
    def sync_function(*args, **kwargs):
106
      with args[0].GetConfigManager(shared):
107
        logging.debug("ConfigWriter.%s(%s, %s)",
108
                      fn.__name__, str(args), str(kwargs))
109
        result = fn(*args, **kwargs)
110
        logging.debug("ConfigWriter.%s(...) returned '%s'",
111
                      fn.__name__, str(result))
112
        return result
113
    return sync_function
114
  return wrap
115

    
116
# job id used for resource management at config upgrade time
117
_UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
118

    
119

    
120
def _ValidateConfig(data):
121
  """Verifies that a configuration objects looks valid.
122

123
  This only verifies the version of the configuration.
124

125
  @raise errors.ConfigurationError: if the version differs from what
126
      we expect
127

128
  """
129
  if data.version != constants.CONFIG_VERSION:
130
    raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
131

    
132

    
133
class TemporaryReservationManager:
134
  """A temporary resource reservation manager.
135

136
  This is used to reserve resources in a job, before using them, making sure
137
  other jobs cannot get them in the meantime.
138

139
  """
140
  def __init__(self):
141
    self._ec_reserved = {}
142

    
143
  def Reserved(self, resource):
144
    for holder_reserved in self._ec_reserved.values():
145
      if resource in holder_reserved:
146
        return True
147
    return False
148

    
149
  def Reserve(self, ec_id, resource):
150
    if self.Reserved(resource):
151
      raise errors.ReservationError("Duplicate reservation for resource '%s'"
152
                                    % str(resource))
153
    if ec_id not in self._ec_reserved:
154
      self._ec_reserved[ec_id] = set([resource])
155
    else:
156
      self._ec_reserved[ec_id].add(resource)
157

    
158
  def DropECReservations(self, ec_id):
159
    if ec_id in self._ec_reserved:
160
      del self._ec_reserved[ec_id]
161

    
162
  def GetReserved(self):
163
    all_reserved = set()
164
    for holder_reserved in self._ec_reserved.values():
165
      all_reserved.update(holder_reserved)
166
    return all_reserved
167

    
168
  def GetECReserved(self, ec_id):
169
    """ Used when you want to retrieve all reservations for a specific
170
        execution context. E.g when commiting reserved IPs for a specific
171
        network.
172

173
    """
174
    ec_reserved = set()
175
    if ec_id in self._ec_reserved:
176
      ec_reserved.update(self._ec_reserved[ec_id])
177
    return ec_reserved
178

    
179
  def Generate(self, existing, generate_one_fn, ec_id):
180
    """Generate a new resource of this type
181

182
    """
183
    assert callable(generate_one_fn)
184

    
185
    all_elems = self.GetReserved()
186
    all_elems.update(existing)
187
    retries = 64
188
    while retries > 0:
189
      new_resource = generate_one_fn()
190
      if new_resource is not None and new_resource not in all_elems:
191
        break
192
    else:
193
      raise errors.ConfigurationError("Not able generate new resource"
194
                                      " (last tried: %s)" % new_resource)
195
    self.Reserve(ec_id, new_resource)
196
    return new_resource
197

    
198

    
199
def _MatchNameComponentIgnoreCase(short_name, names):
200
  """Wrapper around L{utils.text.MatchNameComponent}.
201

202
  """
203
  return utils.MatchNameComponent(short_name, names, case_sensitive=False)
204

    
205

    
206
def _CheckInstanceDiskIvNames(disks):
207
  """Checks if instance's disks' C{iv_name} attributes are in order.
208

209
  @type disks: list of L{objects.Disk}
210
  @param disks: List of disks
211
  @rtype: list of tuples; (int, string, string)
212
  @return: List of wrongly named disks, each tuple contains disk index,
213
    expected and actual name
214

215
  """
216
  result = []
217

    
218
  for (idx, disk) in enumerate(disks):
219
    exp_iv_name = "disk/%s" % idx
220
    if disk.iv_name != exp_iv_name:
221
      result.append((idx, exp_iv_name, disk.iv_name))
222

    
223
  return result
224

    
225

    
226
class ConfigManager(object):
227
  """Locks the configuration and exposes it to be read or modified.
228

229
  """
230
  def __init__(self, config_writer, shared=False):
231
    assert isinstance(config_writer, ConfigWriter), \
232
           "invalid argument: Not a ConfigWriter"
233
    self._config_writer = config_writer
234
    self._shared = shared
235

    
236
  def __enter__(self):
237
    try:
238
      self._config_writer._OpenConfig(self._shared) # pylint: disable=W0212
239
    except Exception:
240
      logging.debug("Opening configuration failed")
241
      try:
242
        self._config_writer._CloseConfig(False) # pylint: disable=W0212
243
      except Exception: # pylint: disable=W0703
244
        logging.debug("Closing configuration failed as well")
245
      raise
246

    
247
  def __exit__(self, exc_type, exc_value, traceback):
248
    # save the configuration, if this was a write opreration that succeeded
249
    if exc_type is not None:
250
      logging.debug("Configuration operation failed,"
251
                    " the changes will not be saved")
252
    # pylint: disable=W0212
253
    self._config_writer._CloseConfig(not self._shared and exc_type is None)
254
    return False
255

    
256

    
257
class ConfigWriter(object):
258
  """The interface to the cluster configuration.
259

260
  @ivar _temporary_lvs: reservation manager for temporary LVs
261
  @ivar _all_rms: a list of all temporary reservation managers
262

263
  """
264
  def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
265
               accept_foreign=False, wconfdcontext=None, wconfd=None):
266
    self.write_count = 0
267
    self._config_data = None
268
    self._SetConfigData(None)
269
    self._offline = offline
270
    if cfg_file is None:
271
      self._cfg_file = pathutils.CLUSTER_CONF_FILE
272
    else:
273
      self._cfg_file = cfg_file
274
    self._getents = _getents
275
    self._temporary_ids = TemporaryReservationManager()
276
    self._temporary_drbds = {}
277
    self._temporary_macs = TemporaryReservationManager()
278
    self._temporary_secrets = TemporaryReservationManager()
279
    self._temporary_lvs = TemporaryReservationManager()
280
    self._temporary_ips = TemporaryReservationManager()
281
    self._all_rms = [self._temporary_ids, self._temporary_macs,
282
                     self._temporary_secrets, self._temporary_lvs,
283
                     self._temporary_ips]
284
    # Note: in order to prevent errors when resolving our name later,
285
    # we compute it here once and reuse it; it's
286
    # better to raise an error before starting to modify the config
287
    # file than after it was modified
288
    self._my_hostname = netutils.Hostname.GetSysName()
289
    self._cfg_id = None
290
    self._wconfdcontext = wconfdcontext
291
    self._wconfd = wconfd
292
    self._accept_foreign = accept_foreign
293
    self._lock_count = 0
294

    
295
  def _ConfigData(self):
296
    return self._config_data
297

    
298
  def _SetConfigData(self, cfg):
299
    self._config_data = cfg
300

    
301
  def _GetWConfdContext(self):
302
    return self._wconfdcontext
303

    
304
  # this method needs to be static, so that we can call it on the class
305
  @staticmethod
306
  def IsCluster():
307
    """Check if the cluster is configured.
308

309
    """
310
    return os.path.exists(pathutils.CLUSTER_CONF_FILE)
311

    
312
  @_ConfigSync(shared=1)
313
  def GetNdParams(self, node):
314
    """Get the node params populated with cluster defaults.
315

316
    @type node: L{objects.Node}
317
    @param node: The node we want to know the params for
318
    @return: A dict with the filled in node params
319

320
    """
321
    nodegroup = self._UnlockedGetNodeGroup(node.group)
322
    return self._ConfigData().cluster.FillND(node, nodegroup)
323

    
324
  @_ConfigSync(shared=1)
325
  def GetNdGroupParams(self, nodegroup):
326
    """Get the node groups params populated with cluster defaults.
327

328
    @type nodegroup: L{objects.NodeGroup}
329
    @param nodegroup: The node group we want to know the params for
330
    @return: A dict with the filled in node group params
331

332
    """
333
    return self._ConfigData().cluster.FillNDGroup(nodegroup)
334

    
335
  @_ConfigSync(shared=1)
336
  def GetInstanceDiskParams(self, instance):
337
    """Get the disk params populated with inherit chain.
338

339
    @type instance: L{objects.Instance}
340
    @param instance: The instance we want to know the params for
341
    @return: A dict with the filled in disk params
342

343
    """
344
    node = self._UnlockedGetNodeInfo(instance.primary_node)
345
    nodegroup = self._UnlockedGetNodeGroup(node.group)
346
    return self._UnlockedGetGroupDiskParams(nodegroup)
347

    
348
  @_ConfigSync(shared=1)
349
  def GetGroupDiskParams(self, group):
350
    """Get the disk params populated with inherit chain.
351

352
    @type group: L{objects.NodeGroup}
353
    @param group: The group we want to know the params for
354
    @return: A dict with the filled in disk params
355

356
    """
357
    return self._UnlockedGetGroupDiskParams(group)
358

    
359
  def _UnlockedGetGroupDiskParams(self, group):
360
    """Get the disk params populated with inherit chain down to node-group.
361

362
    @type group: L{objects.NodeGroup}
363
    @param group: The group we want to know the params for
364
    @return: A dict with the filled in disk params
365

366
    """
367
    data = self._ConfigData().cluster.SimpleFillDP(group.diskparams)
368
    assert isinstance(data, dict), "Not a dictionary: " + str(data)
369
    return data
370

    
371
  def _UnlockedGetNetworkMACPrefix(self, net_uuid):
372
    """Return the network mac prefix if it exists or the cluster level default.
373

374
    """
375
    prefix = None
376
    if net_uuid:
377
      nobj = self._UnlockedGetNetwork(net_uuid)
378
      if nobj.mac_prefix:
379
        prefix = nobj.mac_prefix
380

    
381
    return prefix
382

    
383
  def _GenerateOneMAC(self, prefix=None):
384
    """Return a function that randomly generates a MAC suffic
385
       and appends it to the given prefix. If prefix is not given get
386
       the cluster level default.
387

388
    """
389
    if not prefix:
390
      prefix = self._ConfigData().cluster.mac_prefix
391

    
392
    def GenMac():
393
      byte1 = random.randrange(0, 256)
394
      byte2 = random.randrange(0, 256)
395
      byte3 = random.randrange(0, 256)
396
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
397
      return mac
398

    
399
    return GenMac
400

    
401
  @_ConfigSync(shared=1)
402
  def GenerateMAC(self, net_uuid, ec_id):
403
    """Generate a MAC for an instance.
404

405
    This should check the current instances for duplicates.
406

407
    """
408
    existing = self._AllMACs()
409
    prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
410
    gen_mac = self._GenerateOneMAC(prefix)
411
    return self._temporary_ids.Generate(existing, gen_mac, ec_id)
412

    
413
  @_ConfigSync(shared=1)
414
  def ReserveMAC(self, mac, ec_id):
415
    """Reserve a MAC for an instance.
416

417
    This only checks instances managed by this cluster, it does not
418
    check for potential collisions elsewhere.
419

420
    """
421
    all_macs = self._AllMACs()
422
    if mac in all_macs:
423
      raise errors.ReservationError("mac already in use")
424
    else:
425
      self._temporary_macs.Reserve(ec_id, mac)
426

    
427
  def _UnlockedCommitTemporaryIps(self, ec_id):
428
    """Commit all reserved IP address to their respective pools
429

430
    """
431
    for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
432
      self._UnlockedCommitIp(action, net_uuid, address)
433

    
434
  def _UnlockedCommitIp(self, action, net_uuid, address):
435
    """Commit a reserved IP address to an IP pool.
436

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

439
    """
440
    nobj = self._UnlockedGetNetwork(net_uuid)
441
    pool = network.AddressPool(nobj)
442
    if action == constants.RESERVE_ACTION:
443
      pool.Reserve(address)
444
    elif action == constants.RELEASE_ACTION:
445
      pool.Release(address)
446

    
447
  def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
448
    """Give a specific IP address back to an IP pool.
449

450
    The IP address is returned to the IP pool designated by pool_id and marked
451
    as reserved.
452

453
    """
454
    self._temporary_ips.Reserve(ec_id,
455
                                (constants.RELEASE_ACTION, address, net_uuid))
456

    
457
  @_ConfigSync(shared=1)
458
  def ReleaseIp(self, net_uuid, address, ec_id):
459
    """Give a specified IP address back to an IP pool.
460

461
    This is just a wrapper around _UnlockedReleaseIp.
462

463
    """
464
    if net_uuid:
465
      self._UnlockedReleaseIp(net_uuid, address, ec_id)
466

    
467
  @_ConfigSync(shared=1)
468
  def GenerateIp(self, net_uuid, ec_id):
469
    """Find a free IPv4 address for an instance.
470

471
    """
472
    nobj = self._UnlockedGetNetwork(net_uuid)
473
    pool = network.AddressPool(nobj)
474

    
475
    def gen_one():
476
      try:
477
        ip = pool.GenerateFree()
478
      except errors.AddressPoolError:
479
        raise errors.ReservationError("Cannot generate IP. Network is full")
480
      return (constants.RESERVE_ACTION, ip, net_uuid)
481

    
482
    _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
483
    return address
484

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

488
    """
489
    nobj = self._UnlockedGetNetwork(net_uuid)
490
    pool = network.AddressPool(nobj)
491
    try:
492
      isreserved = pool.IsReserved(address)
493
      isextreserved = pool.IsReserved(address, external=True)
494
    except errors.AddressPoolError:
495
      raise errors.ReservationError("IP address not in network")
496
    if isreserved:
497
      raise errors.ReservationError("IP address already in use")
498
    if check and isextreserved:
499
      raise errors.ReservationError("IP is externally reserved")
500

    
501
    return self._temporary_ips.Reserve(ec_id,
502
                                       (constants.RESERVE_ACTION,
503
                                        address, net_uuid))
504

    
505
  @_ConfigSync(shared=1)
506
  def ReserveIp(self, net_uuid, address, ec_id, check=True):
507
    """Reserve a given IPv4 address for use by an instance.
508

509
    """
510
    if net_uuid:
511
      return self._UnlockedReserveIp(net_uuid, address, ec_id, check)
512

    
513
  @_ConfigSync(shared=1)
514
  def ReserveLV(self, lv_name, ec_id):
515
    """Reserve an VG/LV pair for an instance.
516

517
    @type lv_name: string
518
    @param lv_name: the logical volume name to reserve
519

520
    """
521
    all_lvs = self._AllLVs()
522
    if lv_name in all_lvs:
523
      raise errors.ReservationError("LV already in use")
524
    else:
525
      self._temporary_lvs.Reserve(ec_id, lv_name)
526

    
527
  @_ConfigSync(shared=1)
528
  def GenerateDRBDSecret(self, ec_id):
529
    """Generate a DRBD secret.
530

531
    This checks the current disks for duplicates.
532

533
    """
534
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
535
                                            utils.GenerateSecret,
536
                                            ec_id)
537

    
538
  def _AllLVs(self):
539
    """Compute the list of all LVs.
540

541
    """
542
    lvnames = set()
543
    for instance in self._ConfigData().instances.values():
544
      node_data = instance.MapLVsByNode()
545
      for lv_list in node_data.values():
546
        lvnames.update(lv_list)
547
    return lvnames
548

    
549
  def _AllDisks(self):
550
    """Compute the list of all Disks (recursively, including children).
551

552
    """
553
    def DiskAndAllChildren(disk):
554
      """Returns a list containing the given disk and all of his children.
555

556
      """
557
      disks = [disk]
558
      if disk.children:
559
        for child_disk in disk.children:
560
          disks.extend(DiskAndAllChildren(child_disk))
561
      return disks
562

    
563
    disks = []
564
    for instance in self._ConfigData().instances.values():
565
      for disk in instance.disks:
566
        disks.extend(DiskAndAllChildren(disk))
567
    return disks
568

    
569
  def _AllNICs(self):
570
    """Compute the list of all NICs.
571

572
    """
573
    nics = []
574
    for instance in self._ConfigData().instances.values():
575
      nics.extend(instance.nics)
576
    return nics
577

    
578
  def _AllIDs(self, include_temporary):
579
    """Compute the list of all UUIDs and names we have.
580

581
    @type include_temporary: boolean
582
    @param include_temporary: whether to include the _temporary_ids set
583
    @rtype: set
584
    @return: a set of IDs
585

586
    """
587
    existing = set()
588
    if include_temporary:
589
      existing.update(self._temporary_ids.GetReserved())
590
    existing.update(self._AllLVs())
591
    existing.update(self._ConfigData().instances.keys())
592
    existing.update(self._ConfigData().nodes.keys())
593
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
594
    return existing
595

    
596
  def _GenerateUniqueID(self, ec_id):
597
    """Generate an unique UUID.
598

599
    This checks the current node, instances and disk names for
600
    duplicates.
601

602
    @rtype: string
603
    @return: the unique id
604

605
    """
606
    existing = self._AllIDs(include_temporary=False)
607
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
608

    
609
  @_ConfigSync(shared=1)
610
  def GenerateUniqueID(self, ec_id):
611
    """Generate an unique ID.
612

613
    This is just a wrapper over the unlocked version.
614

615
    @type ec_id: string
616
    @param ec_id: unique id for the job to reserve the id to
617

618
    """
619
    return self._GenerateUniqueID(ec_id)
620

    
621
  def _AllMACs(self):
622
    """Return all MACs present in the config.
623

624
    @rtype: list
625
    @return: the list of all MACs
626

627
    """
628
    result = []
629
    for instance in self._ConfigData().instances.values():
630
      for nic in instance.nics:
631
        result.append(nic.mac)
632

    
633
    return result
634

    
635
  def _AllDRBDSecrets(self):
636
    """Return all DRBD secrets present in the config.
637

638
    @rtype: list
639
    @return: the list of all DRBD secrets
640

641
    """
642
    def helper(disk, result):
643
      """Recursively gather secrets from this disk."""
644
      if disk.dev_type == constants.DT_DRBD8:
645
        result.append(disk.logical_id[5])
646
      if disk.children:
647
        for child in disk.children:
648
          helper(child, result)
649

    
650
    result = []
651
    for instance in self._ConfigData().instances.values():
652
      for disk in instance.disks:
653
        helper(disk, result)
654

    
655
    return result
656

    
657
  def _CheckDiskIDs(self, disk, l_ids):
658
    """Compute duplicate disk IDs
659

660
    @type disk: L{objects.Disk}
661
    @param disk: the disk at which to start searching
662
    @type l_ids: list
663
    @param l_ids: list of current logical ids
664
    @rtype: list
665
    @return: a list of error messages
666

667
    """
668
    result = []
669
    if disk.logical_id is not None:
670
      if disk.logical_id in l_ids:
671
        result.append("duplicate logical id %s" % str(disk.logical_id))
672
      else:
673
        l_ids.append(disk.logical_id)
674

    
675
    if disk.children:
676
      for child in disk.children:
677
        result.extend(self._CheckDiskIDs(child, l_ids))
678
    return result
679

    
680
  def _UnlockedVerifyConfig(self):
681
    """Verify function.
682

683
    @rtype: list
684
    @return: a list of error messages; a non-empty list signifies
685
        configuration errors
686

687
    """
688
    # pylint: disable=R0914
689
    result = []
690
    seen_macs = []
691
    ports = {}
692
    data = self._ConfigData()
693
    cluster = data.cluster
694
    seen_lids = []
695

    
696
    # global cluster checks
697
    if not cluster.enabled_hypervisors:
698
      result.append("enabled hypervisors list doesn't have any entries")
699
    invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
700
    if invalid_hvs:
701
      result.append("enabled hypervisors contains invalid entries: %s" %
702
                    utils.CommaJoin(invalid_hvs))
703
    missing_hvp = (set(cluster.enabled_hypervisors) -
704
                   set(cluster.hvparams.keys()))
705
    if missing_hvp:
706
      result.append("hypervisor parameters missing for the enabled"
707
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
708

    
709
    if not cluster.enabled_disk_templates:
710
      result.append("enabled disk templates list doesn't have any entries")
711
    invalid_disk_templates = set(cluster.enabled_disk_templates) \
712
                               - constants.DISK_TEMPLATES
713
    if invalid_disk_templates:
714
      result.append("enabled disk templates list contains invalid entries:"
715
                    " %s" % utils.CommaJoin(invalid_disk_templates))
716

    
717
    if cluster.master_node not in data.nodes:
718
      result.append("cluster has invalid primary node '%s'" %
719
                    cluster.master_node)
720

    
721
    def _helper(owner, attr, value, template):
722
      try:
723
        utils.ForceDictType(value, template)
724
      except errors.GenericError, err:
725
        result.append("%s has invalid %s: %s" % (owner, attr, err))
726

    
727
    def _helper_nic(owner, params):
728
      try:
729
        objects.NIC.CheckParameterSyntax(params)
730
      except errors.ConfigurationError, err:
731
        result.append("%s has invalid nicparams: %s" % (owner, err))
732

    
733
    def _helper_ipolicy(owner, ipolicy, iscluster):
734
      try:
735
        objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
736
      except errors.ConfigurationError, err:
737
        result.append("%s has invalid instance policy: %s" % (owner, err))
738
      for key, value in ipolicy.items():
739
        if key == constants.ISPECS_MINMAX:
740
          for k in range(len(value)):
741
            _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
742
        elif key == constants.ISPECS_STD:
743
          _helper(owner, "ipolicy/" + key, value,
744
                  constants.ISPECS_PARAMETER_TYPES)
745
        else:
746
          # FIXME: assuming list type
747
          if key in constants.IPOLICY_PARAMETERS:
748
            exp_type = float
749
            # if the value is int, it can be converted into float
750
            convertible_types = [int]
751
          else:
752
            exp_type = list
753
            convertible_types = []
754
          # Try to convert from allowed types, if necessary.
755
          if any(isinstance(value, ct) for ct in convertible_types):
756
            try:
757
              value = exp_type(value)
758
              ipolicy[key] = value
759
            except ValueError:
760
              pass
761
          if not isinstance(value, exp_type):
762
            result.append("%s has invalid instance policy: for %s,"
763
                          " expecting %s, got %s" %
764
                          (owner, key, exp_type.__name__, type(value)))
765

    
766
    def _helper_ispecs(owner, parentkey, params):
767
      for (key, value) in params.items():
768
        fullkey = "/".join([parentkey, key])
769
        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
770

    
771
    # check cluster parameters
772
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
773
            constants.BES_PARAMETER_TYPES)
774
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
775
            constants.NICS_PARAMETER_TYPES)
776
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
777
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
778
            constants.NDS_PARAMETER_TYPES)
779
    _helper_ipolicy("cluster", cluster.ipolicy, True)
780

    
781
    for disk_template in cluster.diskparams:
782
      if disk_template not in constants.DTS_HAVE_ACCESS:
783
        continue
784

    
785
      access = cluster.diskparams[disk_template].get(constants.LDP_ACCESS,
786
                                                     constants.DISK_KERNELSPACE)
787
      if access not in constants.DISK_VALID_ACCESS_MODES:
788
        result.append(
789
          "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
790
            disk_template, constants.LDP_ACCESS, access,
791
            utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
792
          )
793
        )
794

    
795
    # per-instance checks
796
    for instance_uuid in data.instances:
797
      instance = data.instances[instance_uuid]
798
      if instance.uuid != instance_uuid:
799
        result.append("instance '%s' is indexed by wrong UUID '%s'" %
800
                      (instance.name, instance_uuid))
801
      if instance.primary_node not in data.nodes:
802
        result.append("instance '%s' has invalid primary node '%s'" %
803
                      (instance.name, instance.primary_node))
804
      for snode in instance.secondary_nodes:
805
        if snode not in data.nodes:
806
          result.append("instance '%s' has invalid secondary node '%s'" %
807
                        (instance.name, snode))
808
      for idx, nic in enumerate(instance.nics):
809
        if nic.mac in seen_macs:
810
          result.append("instance '%s' has NIC %d mac %s duplicate" %
811
                        (instance.name, idx, nic.mac))
812
        else:
813
          seen_macs.append(nic.mac)
814
        if nic.nicparams:
815
          filled = cluster.SimpleFillNIC(nic.nicparams)
816
          owner = "instance %s nic %d" % (instance.name, idx)
817
          _helper(owner, "nicparams",
818
                  filled, constants.NICS_PARAMETER_TYPES)
819
          _helper_nic(owner, filled)
820

    
821
      # disk template checks
822
      if not instance.disk_template in data.cluster.enabled_disk_templates:
823
        result.append("instance '%s' uses the disabled disk template '%s'." %
824
                      (instance.name, instance.disk_template))
825

    
826
      # parameter checks
827
      if instance.beparams:
828
        _helper("instance %s" % instance.name, "beparams",
829
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
830

    
831
      # gather the drbd ports for duplicate checks
832
      for (idx, dsk) in enumerate(instance.disks):
833
        if dsk.dev_type in constants.DTS_DRBD:
834
          tcp_port = dsk.logical_id[2]
835
          if tcp_port not in ports:
836
            ports[tcp_port] = []
837
          ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
838
      # gather network port reservation
839
      net_port = getattr(instance, "network_port", None)
840
      if net_port is not None:
841
        if net_port not in ports:
842
          ports[net_port] = []
843
        ports[net_port].append((instance.name, "network port"))
844

    
845
      # instance disk verify
846
      for idx, disk in enumerate(instance.disks):
847
        result.extend(["instance '%s' disk %d error: %s" %
848
                       (instance.name, idx, msg) for msg in disk.Verify()])
849
        result.extend(self._CheckDiskIDs(disk, seen_lids))
850

    
851
      wrong_names = _CheckInstanceDiskIvNames(instance.disks)
852
      if wrong_names:
853
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
854
                         (idx, exp_name, actual_name))
855
                        for (idx, exp_name, actual_name) in wrong_names)
856

    
857
        result.append("Instance '%s' has wrongly named disks: %s" %
858
                      (instance.name, tmp))
859

    
860
    # cluster-wide pool of free ports
861
    for free_port in cluster.tcpudp_port_pool:
862
      if free_port not in ports:
863
        ports[free_port] = []
864
      ports[free_port].append(("cluster", "port marked as free"))
865

    
866
    # compute tcp/udp duplicate ports
867
    keys = ports.keys()
868
    keys.sort()
869
    for pnum in keys:
870
      pdata = ports[pnum]
871
      if len(pdata) > 1:
872
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
873
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
874

    
875
    # highest used tcp port check
876
    if keys:
877
      if keys[-1] > cluster.highest_used_port:
878
        result.append("Highest used port mismatch, saved %s, computed %s" %
879
                      (cluster.highest_used_port, keys[-1]))
880

    
881
    if not data.nodes[cluster.master_node].master_candidate:
882
      result.append("Master node is not a master candidate")
883

    
884
    # master candidate checks
885
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
886
    if mc_now < mc_max:
887
      result.append("Not enough master candidates: actual %d, target %d" %
888
                    (mc_now, mc_max))
889

    
890
    # node checks
891
    for node_uuid, node in data.nodes.items():
892
      if node.uuid != node_uuid:
893
        result.append("Node '%s' is indexed by wrong UUID '%s'" %
894
                      (node.name, node_uuid))
895
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
896
        result.append("Node %s state is invalid: master_candidate=%s,"
897
                      " drain=%s, offline=%s" %
898
                      (node.name, node.master_candidate, node.drained,
899
                       node.offline))
900
      if node.group not in data.nodegroups:
901
        result.append("Node '%s' has invalid group '%s'" %
902
                      (node.name, node.group))
903
      else:
904
        _helper("node %s" % node.name, "ndparams",
905
                cluster.FillND(node, data.nodegroups[node.group]),
906
                constants.NDS_PARAMETER_TYPES)
907
      used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
908
      if used_globals:
909
        result.append("Node '%s' has some global parameters set: %s" %
910
                      (node.name, utils.CommaJoin(used_globals)))
911

    
912
    # nodegroups checks
913
    nodegroups_names = set()
914
    for nodegroup_uuid in data.nodegroups:
915
      nodegroup = data.nodegroups[nodegroup_uuid]
916
      if nodegroup.uuid != nodegroup_uuid:
917
        result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
918
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
919
      if utils.UUID_RE.match(nodegroup.name.lower()):
920
        result.append("node group '%s' (uuid: '%s') has uuid-like name" %
921
                      (nodegroup.name, nodegroup.uuid))
922
      if nodegroup.name in nodegroups_names:
923
        result.append("duplicate node group name '%s'" % nodegroup.name)
924
      else:
925
        nodegroups_names.add(nodegroup.name)
926
      group_name = "group %s" % nodegroup.name
927
      _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
928
                      False)
929
      if nodegroup.ndparams:
930
        _helper(group_name, "ndparams",
931
                cluster.SimpleFillND(nodegroup.ndparams),
932
                constants.NDS_PARAMETER_TYPES)
933

    
934
    # drbd minors check
935
    _, duplicates = self._UnlockedComputeDRBDMap()
936
    for node, minor, instance_a, instance_b in duplicates:
937
      result.append("DRBD minor %d on node %s is assigned twice to instances"
938
                    " %s and %s" % (minor, node, instance_a, instance_b))
939

    
940
    # IP checks
941
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
942
    ips = {}
943

    
944
    def _AddIpAddress(ip, name):
945
      ips.setdefault(ip, []).append(name)
946

    
947
    _AddIpAddress(cluster.master_ip, "cluster_ip")
948

    
949
    for node in data.nodes.values():
950
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
951
      if node.secondary_ip != node.primary_ip:
952
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
953

    
954
    for instance in data.instances.values():
955
      for idx, nic in enumerate(instance.nics):
956
        if nic.ip is None:
957
          continue
958

    
959
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
960
        nic_mode = nicparams[constants.NIC_MODE]
961
        nic_link = nicparams[constants.NIC_LINK]
962

    
963
        if nic_mode == constants.NIC_MODE_BRIDGED:
964
          link = "bridge:%s" % nic_link
965
        elif nic_mode == constants.NIC_MODE_ROUTED:
966
          link = "route:%s" % nic_link
967
        else:
968
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
969

    
970
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
971
                      "instance:%s/nic:%d" % (instance.name, idx))
972

    
973
    for ip, owners in ips.items():
974
      if len(owners) > 1:
975
        result.append("IP address %s is used by multiple owners: %s" %
976
                      (ip, utils.CommaJoin(owners)))
977

    
978
    return result
979

    
980
  @_ConfigSync(shared=1)
981
  def VerifyConfig(self):
982
    """Verify function.
983

984
    This is just a wrapper over L{_UnlockedVerifyConfig}.
985

986
    @rtype: list
987
    @return: a list of error messages; a non-empty list signifies
988
        configuration errors
989

990
    """
991
    return self._UnlockedVerifyConfig()
992

    
993
  @_ConfigSync()
994
  def AddTcpUdpPort(self, port):
995
    """Adds a new port to the available port pool.
996

997
    @warning: this method does not "flush" the configuration (via
998
        L{_WriteConfig}); callers should do that themselves once the
999
        configuration is stable
1000

1001
    """
1002
    if not isinstance(port, int):
1003
      raise errors.ProgrammerError("Invalid type passed for port")
1004

    
1005
    self._ConfigData().cluster.tcpudp_port_pool.add(port)
1006

    
1007
  @_ConfigSync(shared=1)
1008
  def GetPortList(self):
1009
    """Returns a copy of the current port list.
1010

1011
    """
1012
    return self._ConfigData().cluster.tcpudp_port_pool.copy()
1013

    
1014
  @_ConfigSync()
1015
  def AllocatePort(self):
1016
    """Allocate a port.
1017

1018
    The port will be taken from the available port pool or from the
1019
    default port range (and in this case we increase
1020
    highest_used_port).
1021

1022
    """
1023
    # If there are TCP/IP ports configured, we use them first.
1024
    if self._ConfigData().cluster.tcpudp_port_pool:
1025
      port = self._ConfigData().cluster.tcpudp_port_pool.pop()
1026
    else:
1027
      port = self._ConfigData().cluster.highest_used_port + 1
1028
      if port >= constants.LAST_DRBD_PORT:
1029
        raise errors.ConfigurationError("The highest used port is greater"
1030
                                        " than %s. Aborting." %
1031
                                        constants.LAST_DRBD_PORT)
1032
      self._ConfigData().cluster.highest_used_port = port
1033
    return port
1034

    
1035
  def _UnlockedComputeDRBDMap(self):
1036
    """Compute the used DRBD minor/nodes.
1037

1038
    @rtype: (dict, list)
1039
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
1040
        the returned dict will have all the nodes in it (even if with
1041
        an empty list), and a list of duplicates; if the duplicates
1042
        list is not empty, the configuration is corrupted and its caller
1043
        should raise an exception
1044

1045
    """
1046
    def _AppendUsedMinors(get_node_name_fn, instance, disk, used):
1047
      duplicates = []
1048
      if disk.dev_type == constants.DT_DRBD8 and len(disk.logical_id) >= 5:
1049
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
1050
        for node_uuid, minor in ((node_a, minor_a), (node_b, minor_b)):
1051
          assert node_uuid in used, \
1052
            ("Node '%s' of instance '%s' not found in node list" %
1053
             (get_node_name_fn(node_uuid), instance.name))
1054
          if minor in used[node_uuid]:
1055
            duplicates.append((node_uuid, minor, instance.uuid,
1056
                               used[node_uuid][minor]))
1057
          else:
1058
            used[node_uuid][minor] = instance.uuid
1059
      if disk.children:
1060
        for child in disk.children:
1061
          duplicates.extend(_AppendUsedMinors(get_node_name_fn, instance, child,
1062
                                              used))
1063
      return duplicates
1064

    
1065
    duplicates = []
1066
    my_dict = dict((node_uuid, {}) for node_uuid in self._ConfigData().nodes)
1067
    for instance in self._ConfigData().instances.itervalues():
1068
      for disk in instance.disks:
1069
        duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
1070
                                            instance, disk, my_dict))
1071
    for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
1072
      if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
1073
        duplicates.append((node_uuid, minor, inst_uuid,
1074
                           my_dict[node_uuid][minor]))
1075
      else:
1076
        my_dict[node_uuid][minor] = inst_uuid
1077
    return my_dict, duplicates
1078

    
1079
  @_ConfigSync()
1080
  def ComputeDRBDMap(self):
1081
    """Compute the used DRBD minor/nodes.
1082

1083
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
1084

1085
    @return: dictionary of node_uuid: dict of minor: instance_uuid;
1086
        the returned dict will have all the nodes in it (even if with
1087
        an empty list).
1088

1089
    """
1090
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1091
    if duplicates:
1092
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1093
                                      str(duplicates))
1094
    return d_map
1095

    
1096
  @_ConfigSync()
1097
  def AllocateDRBDMinor(self, node_uuids, inst_uuid):
1098
    """Allocate a drbd minor.
1099

1100
    The free minor will be automatically computed from the existing
1101
    devices. A node can be given multiple times in order to allocate
1102
    multiple minors. The result is the list of minors, in the same
1103
    order as the passed nodes.
1104

1105
    @type inst_uuid: string
1106
    @param inst_uuid: the instance for which we allocate minors
1107

1108
    """
1109
    assert isinstance(inst_uuid, basestring), \
1110
           "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
1111

    
1112
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1113
    if duplicates:
1114
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1115
                                      str(duplicates))
1116
    result = []
1117
    for nuuid in node_uuids:
1118
      ndata = d_map[nuuid]
1119
      if not ndata:
1120
        # no minors used, we can start at 0
1121
        result.append(0)
1122
        ndata[0] = inst_uuid
1123
        self._temporary_drbds[(nuuid, 0)] = inst_uuid
1124
        continue
1125
      keys = ndata.keys()
1126
      keys.sort()
1127
      ffree = utils.FirstFree(keys)
1128
      if ffree is None:
1129
        # return the next minor
1130
        # TODO: implement high-limit check
1131
        minor = keys[-1] + 1
1132
      else:
1133
        minor = ffree
1134
      # double-check minor against current instances
1135
      assert minor not in d_map[nuuid], \
1136
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
1137
              " already allocated to instance %s" %
1138
              (minor, nuuid, d_map[nuuid][minor]))
1139
      ndata[minor] = inst_uuid
1140
      # double-check minor against reservation
1141
      r_key = (nuuid, minor)
1142
      assert r_key not in self._temporary_drbds, \
1143
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
1144
              " reserved for instance %s" %
1145
              (minor, nuuid, self._temporary_drbds[r_key]))
1146
      self._temporary_drbds[r_key] = inst_uuid
1147
      result.append(minor)
1148
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1149
                  node_uuids, result)
1150
    return result
1151

    
1152
  def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1153
    """Release temporary drbd minors allocated for a given instance.
1154

1155
    @type inst_uuid: string
1156
    @param inst_uuid: the instance for which temporary minors should be
1157
                      released
1158

1159
    """
1160
    assert isinstance(inst_uuid, basestring), \
1161
           "Invalid argument passed to ReleaseDRBDMinors"
1162
    for key, uuid in self._temporary_drbds.items():
1163
      if uuid == inst_uuid:
1164
        del self._temporary_drbds[key]
1165

    
1166
  @_ConfigSync()
1167
  def ReleaseDRBDMinors(self, inst_uuid):
1168
    """Release temporary drbd minors allocated for a given instance.
1169

1170
    This should be called on the error paths, on the success paths
1171
    it's automatically called by the ConfigWriter add and update
1172
    functions.
1173

1174
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1175

1176
    @type inst_uuid: string
1177
    @param inst_uuid: the instance for which temporary minors should be
1178
                      released
1179

1180
    """
1181
    self._UnlockedReleaseDRBDMinors(inst_uuid)
1182

    
1183
  @_ConfigSync(shared=1)
1184
  def GetConfigVersion(self):
1185
    """Get the configuration version.
1186

1187
    @return: Config version
1188

1189
    """
1190
    return self._ConfigData().version
1191

    
1192
  @_ConfigSync(shared=1)
1193
  def GetClusterName(self):
1194
    """Get cluster name.
1195

1196
    @return: Cluster name
1197

1198
    """
1199
    return self._ConfigData().cluster.cluster_name
1200

    
1201
  @_ConfigSync(shared=1)
1202
  def GetMasterNode(self):
1203
    """Get the UUID of the master node for this cluster.
1204

1205
    @return: Master node UUID
1206

1207
    """
1208
    return self._ConfigData().cluster.master_node
1209

    
1210
  @_ConfigSync(shared=1)
1211
  def GetMasterNodeName(self):
1212
    """Get the hostname of the master node for this cluster.
1213

1214
    @return: Master node hostname
1215

1216
    """
1217
    return self._UnlockedGetNodeName(self._ConfigData().cluster.master_node)
1218

    
1219
  @_ConfigSync(shared=1)
1220
  def GetMasterNodeInfo(self):
1221
    """Get the master node information for this cluster.
1222

1223
    @rtype: objects.Node
1224
    @return: Master node L{objects.Node} object
1225

1226
    """
1227
    return self._UnlockedGetNodeInfo(self._ConfigData().cluster.master_node)
1228

    
1229
  @_ConfigSync(shared=1)
1230
  def GetMasterIP(self):
1231
    """Get the IP of the master node for this cluster.
1232

1233
    @return: Master IP
1234

1235
    """
1236
    return self._ConfigData().cluster.master_ip
1237

    
1238
  @_ConfigSync(shared=1)
1239
  def GetMasterNetdev(self):
1240
    """Get the master network device for this cluster.
1241

1242
    """
1243
    return self._ConfigData().cluster.master_netdev
1244

    
1245
  @_ConfigSync(shared=1)
1246
  def GetMasterNetmask(self):
1247
    """Get the netmask of the master node for this cluster.
1248

1249
    """
1250
    return self._ConfigData().cluster.master_netmask
1251

    
1252
  @_ConfigSync(shared=1)
1253
  def GetUseExternalMipScript(self):
1254
    """Get flag representing whether to use the external master IP setup script.
1255

1256
    """
1257
    return self._ConfigData().cluster.use_external_mip_script
1258

    
1259
  @_ConfigSync(shared=1)
1260
  def GetFileStorageDir(self):
1261
    """Get the file storage dir for this cluster.
1262

1263
    """
1264
    return self._ConfigData().cluster.file_storage_dir
1265

    
1266
  @_ConfigSync(shared=1)
1267
  def GetSharedFileStorageDir(self):
1268
    """Get the shared file storage dir for this cluster.
1269

1270
    """
1271
    return self._ConfigData().cluster.shared_file_storage_dir
1272

    
1273
  @_ConfigSync(shared=1)
1274
  def GetGlusterStorageDir(self):
1275
    """Get the Gluster storage dir for this cluster.
1276

1277
    """
1278
    return self._ConfigData().cluster.gluster_storage_dir
1279

    
1280
  @_ConfigSync(shared=1)
1281
  def GetHypervisorType(self):
1282
    """Get the hypervisor type for this cluster.
1283

1284
    """
1285
    return self._ConfigData().cluster.enabled_hypervisors[0]
1286

    
1287
  @_ConfigSync(shared=1)
1288
  def GetRsaHostKey(self):
1289
    """Return the rsa hostkey from the config.
1290

1291
    @rtype: string
1292
    @return: the rsa hostkey
1293

1294
    """
1295
    return self._ConfigData().cluster.rsahostkeypub
1296

    
1297
  @_ConfigSync(shared=1)
1298
  def GetDsaHostKey(self):
1299
    """Return the dsa hostkey from the config.
1300

1301
    @rtype: string
1302
    @return: the dsa hostkey
1303

1304
    """
1305
    return self._ConfigData().cluster.dsahostkeypub
1306

    
1307
  @_ConfigSync(shared=1)
1308
  def GetDefaultIAllocator(self):
1309
    """Get the default instance allocator for this cluster.
1310

1311
    """
1312
    return self._ConfigData().cluster.default_iallocator
1313

    
1314
  @_ConfigSync(shared=1)
1315
  def GetDefaultIAllocatorParameters(self):
1316
    """Get the default instance allocator parameters for this cluster.
1317

1318
    @rtype: dict
1319
    @return: dict of iallocator parameters
1320

1321
    """
1322
    return self._ConfigData().cluster.default_iallocator_params
1323

    
1324
  @_ConfigSync(shared=1)
1325
  def GetPrimaryIPFamily(self):
1326
    """Get cluster primary ip family.
1327

1328
    @return: primary ip family
1329

1330
    """
1331
    return self._ConfigData().cluster.primary_ip_family
1332

    
1333
  @_ConfigSync(shared=1)
1334
  def GetMasterNetworkParameters(self):
1335
    """Get network parameters of the master node.
1336

1337
    @rtype: L{object.MasterNetworkParameters}
1338
    @return: network parameters of the master node
1339

1340
    """
1341
    cluster = self._ConfigData().cluster
1342
    result = objects.MasterNetworkParameters(
1343
      uuid=cluster.master_node, ip=cluster.master_ip,
1344
      netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1345
      ip_family=cluster.primary_ip_family)
1346

    
1347
    return result
1348

    
1349
  @_ConfigSync(shared=1)
1350
  def GetInstanceCommunicationNetwork(self):
1351
    """Get cluster instance communication network
1352

1353
    @rtype: string
1354
    @return: instance communication network, which is the name of the
1355
             network used for instance communication
1356

1357
    """
1358
    return self._ConfigData().cluster.instance_communication_network
1359

    
1360
  @_ConfigSync()
1361
  def SetInstanceCommunicationNetwork(self, network_name):
1362
    """Set cluster instance communication network
1363

1364
    @type network_name: string
1365
    @param network_name: instance communication network, which is the name of
1366
                         the network used for instance communication
1367

1368
    """
1369
    self._ConfigData().cluster.instance_communication_network = network_name
1370

    
1371
  @_ConfigSync()
1372
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1373
    """Add a node group to the configuration.
1374

1375
    This method calls group.UpgradeConfig() to fill any missing attributes
1376
    according to their default values.
1377

1378
    @type group: L{objects.NodeGroup}
1379
    @param group: the NodeGroup object to add
1380
    @type ec_id: string
1381
    @param ec_id: unique id for the job to use when creating a missing UUID
1382
    @type check_uuid: bool
1383
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1384
                       it does, ensure that it does not exist in the
1385
                       configuration already
1386

1387
    """
1388
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1389

    
1390
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1391
    """Add a node group to the configuration.
1392

1393
    """
1394
    logging.info("Adding node group %s to configuration", group.name)
1395

    
1396
    # Some code might need to add a node group with a pre-populated UUID
1397
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1398
    # the "does this UUID" exist already check.
1399
    if check_uuid:
1400
      self._EnsureUUID(group, ec_id)
1401

    
1402
    try:
1403
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1404
    except errors.OpPrereqError:
1405
      pass
1406
    else:
1407
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1408
                                 " node group (UUID: %s)" %
1409
                                 (group.name, existing_uuid),
1410
                                 errors.ECODE_EXISTS)
1411

    
1412
    group.serial_no = 1
1413
    group.ctime = group.mtime = time.time()
1414
    group.UpgradeConfig()
1415

    
1416
    self._ConfigData().nodegroups[group.uuid] = group
1417
    self._ConfigData().cluster.serial_no += 1
1418

    
1419
  @_ConfigSync()
1420
  def RemoveNodeGroup(self, group_uuid):
1421
    """Remove a node group from the configuration.
1422

1423
    @type group_uuid: string
1424
    @param group_uuid: the UUID of the node group to remove
1425

1426
    """
1427
    logging.info("Removing node group %s from configuration", group_uuid)
1428

    
1429
    if group_uuid not in self._ConfigData().nodegroups:
1430
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1431

    
1432
    assert len(self._ConfigData().nodegroups) != 1, \
1433
            "Group '%s' is the only group, cannot be removed" % group_uuid
1434

    
1435
    del self._ConfigData().nodegroups[group_uuid]
1436
    self._ConfigData().cluster.serial_no += 1
1437

    
1438
  def _UnlockedLookupNodeGroup(self, target):
1439
    """Lookup a node group's UUID.
1440

1441
    @type target: string or None
1442
    @param target: group name or UUID or None to look for the default
1443
    @rtype: string
1444
    @return: nodegroup UUID
1445
    @raises errors.OpPrereqError: when the target group cannot be found
1446

1447
    """
1448
    if target is None:
1449
      if len(self._ConfigData().nodegroups) != 1:
1450
        raise errors.OpPrereqError("More than one node group exists. Target"
1451
                                   " group must be specified explicitly.")
1452
      else:
1453
        return self._ConfigData().nodegroups.keys()[0]
1454
    if target in self._ConfigData().nodegroups:
1455
      return target
1456
    for nodegroup in self._ConfigData().nodegroups.values():
1457
      if nodegroup.name == target:
1458
        return nodegroup.uuid
1459
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1460
                               errors.ECODE_NOENT)
1461

    
1462
  @_ConfigSync(shared=1)
1463
  def LookupNodeGroup(self, target):
1464
    """Lookup a node group's UUID.
1465

1466
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1467

1468
    @type target: string or None
1469
    @param target: group name or UUID or None to look for the default
1470
    @rtype: string
1471
    @return: nodegroup UUID
1472

1473
    """
1474
    return self._UnlockedLookupNodeGroup(target)
1475

    
1476
  def _UnlockedGetNodeGroup(self, uuid):
1477
    """Lookup a node group.
1478

1479
    @type uuid: string
1480
    @param uuid: group UUID
1481
    @rtype: L{objects.NodeGroup} or None
1482
    @return: nodegroup object, or None if not found
1483

1484
    """
1485
    if uuid not in self._ConfigData().nodegroups:
1486
      return None
1487

    
1488
    return self._ConfigData().nodegroups[uuid]
1489

    
1490
  @_ConfigSync(shared=1)
1491
  def GetNodeGroup(self, uuid):
1492
    """Lookup a node group.
1493

1494
    @type uuid: string
1495
    @param uuid: group UUID
1496
    @rtype: L{objects.NodeGroup} or None
1497
    @return: nodegroup object, or None if not found
1498

1499
    """
1500
    return self._UnlockedGetNodeGroup(uuid)
1501

    
1502
  def _UnlockedGetAllNodeGroupsInfo(self):
1503
    """Get the configuration of all node groups.
1504

1505
    """
1506
    return dict(self._ConfigData().nodegroups)
1507

    
1508
  @_ConfigSync(shared=1)
1509
  def GetAllNodeGroupsInfo(self):
1510
    """Get the configuration of all node groups.
1511

1512
    """
1513
    return self._UnlockedGetAllNodeGroupsInfo()
1514

    
1515
  @_ConfigSync(shared=1)
1516
  def GetAllNodeGroupsInfoDict(self):
1517
    """Get the configuration of all node groups expressed as a dictionary of
1518
    dictionaries.
1519

1520
    """
1521
    return dict(map(lambda (uuid, ng): (uuid, ng.ToDict()),
1522
                    self._UnlockedGetAllNodeGroupsInfo().items()))
1523

    
1524
  @_ConfigSync(shared=1)
1525
  def GetNodeGroupList(self):
1526
    """Get a list of node groups.
1527

1528
    """
1529
    return self._ConfigData().nodegroups.keys()
1530

    
1531
  @_ConfigSync(shared=1)
1532
  def GetNodeGroupMembersByNodes(self, nodes):
1533
    """Get nodes which are member in the same nodegroups as the given nodes.
1534

1535
    """
1536
    ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1537
    return frozenset(member_uuid
1538
                     for node_uuid in nodes
1539
                     for member_uuid in
1540
                       self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1541

    
1542
  @_ConfigSync(shared=1)
1543
  def GetMultiNodeGroupInfo(self, group_uuids):
1544
    """Get the configuration of multiple node groups.
1545

1546
    @param group_uuids: List of node group UUIDs
1547
    @rtype: list
1548
    @return: List of tuples of (group_uuid, group_info)
1549

1550
    """
1551
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1552

    
1553
  @_ConfigSync()
1554
  def AddInstance(self, instance, ec_id):
1555
    """Add an instance to the config.
1556

1557
    This should be used after creating a new instance.
1558

1559
    @type instance: L{objects.Instance}
1560
    @param instance: the instance object
1561

1562
    """
1563
    if not isinstance(instance, objects.Instance):
1564
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1565

    
1566
    if instance.disk_template != constants.DT_DISKLESS:
1567
      all_lvs = instance.MapLVsByNode()
1568
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1569

    
1570
    all_macs = self._AllMACs()
1571
    for nic in instance.nics:
1572
      if nic.mac in all_macs:
1573
        raise errors.ConfigurationError("Cannot add instance %s:"
1574
                                        " MAC address '%s' already in use." %
1575
                                        (instance.name, nic.mac))
1576

    
1577
    self._CheckUniqueUUID(instance, include_temporary=False)
1578

    
1579
    instance.serial_no = 1
1580
    instance.ctime = instance.mtime = time.time()
1581
    self._ConfigData().instances[instance.uuid] = instance
1582
    self._ConfigData().cluster.serial_no += 1
1583
    self._UnlockedReleaseDRBDMinors(instance.uuid)
1584
    self._UnlockedCommitTemporaryIps(ec_id)
1585

    
1586
  def _EnsureUUID(self, item, ec_id):
1587
    """Ensures a given object has a valid UUID.
1588

1589
    @param item: the instance or node to be checked
1590
    @param ec_id: the execution context id for the uuid reservation
1591

1592
    """
1593
    if not item.uuid:
1594
      item.uuid = self._GenerateUniqueID(ec_id)
1595
    else:
1596
      self._CheckUniqueUUID(item, include_temporary=True)
1597

    
1598
  def _CheckUniqueUUID(self, item, include_temporary):
1599
    """Checks that the UUID of the given object is unique.
1600

1601
    @param item: the instance or node to be checked
1602
    @param include_temporary: whether temporarily generated UUID's should be
1603
              included in the check. If the UUID of the item to be checked is
1604
              a temporarily generated one, this has to be C{False}.
1605

1606
    """
1607
    if not item.uuid:
1608
      raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1609
    if item.uuid in self._AllIDs(include_temporary=include_temporary):
1610
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1611
                                      " in use" % (item.name, item.uuid))
1612

    
1613
  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1614
    """Set the instance's status to a given value.
1615

1616
    @rtype: L{objects.Instance}
1617
    @return: the updated instance object
1618

1619
    """
1620
    if inst_uuid not in self._ConfigData().instances:
1621
      raise errors.ConfigurationError("Unknown instance '%s'" %
1622
                                      inst_uuid)
1623
    instance = self._ConfigData().instances[inst_uuid]
1624

    
1625
    if status is None:
1626
      status = instance.admin_state
1627
    if disks_active is None:
1628
      disks_active = instance.disks_active
1629

    
1630
    assert status in constants.ADMINST_ALL, \
1631
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1632

    
1633
    if instance.admin_state != status or \
1634
       instance.disks_active != disks_active:
1635
      instance.admin_state = status
1636
      instance.disks_active = disks_active
1637
      instance.serial_no += 1
1638
      instance.mtime = time.time()
1639
    return instance
1640

    
1641
  @_ConfigSync()
1642
  def MarkInstanceUp(self, inst_uuid):
1643
    """Mark the instance status to up in the config.
1644

1645
    This also sets the instance disks active flag.
1646

1647
    @rtype: L{objects.Instance}
1648
    @return: the updated instance object
1649

1650
    """
1651
    return self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1652

    
1653
  @_ConfigSync()
1654
  def MarkInstanceOffline(self, inst_uuid):
1655
    """Mark the instance status to down in the config.
1656

1657
    This also clears the instance disks active flag.
1658

1659
    @rtype: L{objects.Instance}
1660
    @return: the updated instance object
1661

1662
    """
1663
    return self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1664

    
1665
  @_ConfigSync()
1666
  def RemoveInstance(self, inst_uuid):
1667
    """Remove the instance from the configuration.
1668

1669
    """
1670
    if inst_uuid not in self._ConfigData().instances:
1671
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1672

    
1673
    # If a network port has been allocated to the instance,
1674
    # return it to the pool of free ports.
1675
    inst = self._ConfigData().instances[inst_uuid]
1676
    network_port = getattr(inst, "network_port", None)
1677
    if network_port is not None:
1678
      self._ConfigData().cluster.tcpudp_port_pool.add(network_port)
1679

    
1680
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1681

    
1682
    for nic in instance.nics:
1683
      if nic.network and nic.ip:
1684
        # Return all IP addresses to the respective address pools
1685
        self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1686

    
1687
    del self._ConfigData().instances[inst_uuid]
1688
    self._ConfigData().cluster.serial_no += 1
1689

    
1690
  @_ConfigSync()
1691
  def RenameInstance(self, inst_uuid, new_name):
1692
    """Rename an instance.
1693

1694
    This needs to be done in ConfigWriter and not by RemoveInstance
1695
    combined with AddInstance as only we can guarantee an atomic
1696
    rename.
1697

1698
    """
1699
    if inst_uuid not in self._ConfigData().instances:
1700
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1701

    
1702
    inst = self._ConfigData().instances[inst_uuid]
1703
    inst.name = new_name
1704

    
1705
    for (_, disk) in enumerate(inst.disks):
1706
      if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1707
        # rename the file paths in logical and physical id
1708
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1709
        disk.logical_id = (disk.logical_id[0],
1710
                           utils.PathJoin(file_storage_dir, inst.name,
1711
                                          os.path.basename(disk.logical_id[1])))
1712

    
1713
    # Force update of ssconf files
1714
    self._ConfigData().cluster.serial_no += 1
1715

    
1716
  @_ConfigSync()
1717
  def MarkInstanceDown(self, inst_uuid):
1718
    """Mark the status of an instance to down in the configuration.
1719

1720
    This does not touch the instance disks active flag, as shut down instances
1721
    can still have active disks.
1722

1723
    @rtype: L{objects.Instance}
1724
    @return: the updated instance object
1725

1726
    """
1727
    return self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1728

    
1729
  @_ConfigSync()
1730
  def MarkInstanceDisksActive(self, inst_uuid):
1731
    """Mark the status of instance disks active.
1732

1733
    @rtype: L{objects.Instance}
1734
    @return: the updated instance object
1735

1736
    """
1737
    return self._SetInstanceStatus(inst_uuid, None, True)
1738

    
1739
  @_ConfigSync()
1740
  def MarkInstanceDisksInactive(self, inst_uuid):
1741
    """Mark the status of instance disks inactive.
1742

1743
    @rtype: L{objects.Instance}
1744
    @return: the updated instance object
1745

1746
    """
1747
    return self._SetInstanceStatus(inst_uuid, None, False)
1748

    
1749
  def _UnlockedGetInstanceList(self):
1750
    """Get the list of instances.
1751

1752
    This function is for internal use, when the config lock is already held.
1753

1754
    """
1755
    return self._ConfigData().instances.keys()
1756

    
1757
  @_ConfigSync(shared=1)
1758
  def GetInstanceList(self):
1759
    """Get the list of instances.
1760

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

1763
    """
1764
    return self._UnlockedGetInstanceList()
1765

    
1766
  def ExpandInstanceName(self, short_name):
1767
    """Attempt to expand an incomplete instance name.
1768

1769
    """
1770
    # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1771
    all_insts = self.GetAllInstancesInfo().values()
1772
    expanded_name = _MatchNameComponentIgnoreCase(
1773
                      short_name, [inst.name for inst in all_insts])
1774

    
1775
    if expanded_name is not None:
1776
      # there has to be exactly one instance with that name
1777
      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1778
      return (inst.uuid, inst.name)
1779
    else:
1780
      return (None, None)
1781

    
1782
  def _UnlockedGetInstanceInfo(self, inst_uuid):
1783
    """Returns information about an instance.
1784

1785
    This function is for internal use, when the config lock is already held.
1786

1787
    """
1788
    if inst_uuid not in self._ConfigData().instances:
1789
      return None
1790

    
1791
    return self._ConfigData().instances[inst_uuid]
1792

    
1793
  @_ConfigSync(shared=1)
1794
  def GetInstanceInfo(self, inst_uuid):
1795
    """Returns information about an instance.
1796

1797
    It takes the information from the configuration file. Other information of
1798
    an instance are taken from the live systems.
1799

1800
    @param inst_uuid: UUID of the instance
1801

1802
    @rtype: L{objects.Instance}
1803
    @return: the instance object
1804

1805
    """
1806
    return self._UnlockedGetInstanceInfo(inst_uuid)
1807

    
1808
  @_ConfigSync(shared=1)
1809
  def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1810
    """Returns set of node group UUIDs for instance's nodes.
1811

1812
    @rtype: frozenset
1813

1814
    """
1815
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1816
    if not instance:
1817
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1818

    
1819
    if primary_only:
1820
      nodes = [instance.primary_node]
1821
    else:
1822
      nodes = instance.all_nodes
1823

    
1824
    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1825
                     for node_uuid in nodes)
1826

    
1827
  @_ConfigSync(shared=1)
1828
  def GetInstanceNetworks(self, inst_uuid):
1829
    """Returns set of network UUIDs for instance's nics.
1830

1831
    @rtype: frozenset
1832

1833
    """
1834
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
1835
    if not instance:
1836
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1837

    
1838
    networks = set()
1839
    for nic in instance.nics:
1840
      if nic.network:
1841
        networks.add(nic.network)
1842

    
1843
    return frozenset(networks)
1844

    
1845
  @_ConfigSync(shared=1)
1846
  def GetMultiInstanceInfo(self, inst_uuids):
1847
    """Get the configuration of multiple instances.
1848

1849
    @param inst_uuids: list of instance UUIDs
1850
    @rtype: list
1851
    @return: list of tuples (instance UUID, instance_info), where
1852
        instance_info is what would GetInstanceInfo return for the
1853
        node, while keeping the original order
1854

1855
    """
1856
    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1857

    
1858
  @_ConfigSync(shared=1)
1859
  def GetMultiInstanceInfoByName(self, inst_names):
1860
    """Get the configuration of multiple instances.
1861

1862
    @param inst_names: list of instance names
1863
    @rtype: list
1864
    @return: list of tuples (instance, instance_info), where
1865
        instance_info is what would GetInstanceInfo return for the
1866
        node, while keeping the original order
1867

1868
    """
1869
    result = []
1870
    for name in inst_names:
1871
      instance = self._UnlockedGetInstanceInfoByName(name)
1872
      result.append((instance.uuid, instance))
1873
    return result
1874

    
1875
  @_ConfigSync(shared=1)
1876
  def GetAllInstancesInfo(self):
1877
    """Get the configuration of all instances.
1878

1879
    @rtype: dict
1880
    @return: dict of (instance, instance_info), where instance_info is what
1881
              would GetInstanceInfo return for the node
1882

1883
    """
1884
    return self._UnlockedGetAllInstancesInfo()
1885

    
1886
  def _UnlockedGetAllInstancesInfo(self):
1887
    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1888
                    for inst_uuid in self._UnlockedGetInstanceList()])
1889
    return my_dict
1890

    
1891
  @_ConfigSync(shared=1)
1892
  def GetInstancesInfoByFilter(self, filter_fn):
1893
    """Get instance configuration with a filter.
1894

1895
    @type filter_fn: callable
1896
    @param filter_fn: Filter function receiving instance object as parameter,
1897
      returning boolean. Important: this function is called while the
1898
      configuration locks is held. It must not do any complex work or call
1899
      functions potentially leading to a deadlock. Ideally it doesn't call any
1900
      other functions and just compares instance attributes.
1901

1902
    """
1903
    return dict((uuid, inst)
1904
                for (uuid, inst) in self._ConfigData().instances.items()
1905
                if filter_fn(inst))
1906

    
1907
  @_ConfigSync(shared=1)
1908
  def GetInstanceInfoByName(self, inst_name):
1909
    """Get the L{objects.Instance} object for a named instance.
1910

1911
    @param inst_name: name of the instance to get information for
1912
    @type inst_name: string
1913
    @return: the corresponding L{objects.Instance} instance or None if no
1914
          information is available
1915

1916
    """
1917
    return self._UnlockedGetInstanceInfoByName(inst_name)
1918

    
1919
  def _UnlockedGetInstanceInfoByName(self, inst_name):
1920
    for inst in self._UnlockedGetAllInstancesInfo().values():
1921
      if inst.name == inst_name:
1922
        return inst
1923
    return None
1924

    
1925
  def _UnlockedGetInstanceName(self, inst_uuid):
1926
    inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1927
    if inst_info is None:
1928
      raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1929
    return inst_info.name
1930

    
1931
  @_ConfigSync(shared=1)
1932
  def GetInstanceName(self, inst_uuid):
1933
    """Gets the instance name for the passed instance.
1934

1935
    @param inst_uuid: instance UUID to get name for
1936
    @type inst_uuid: string
1937
    @rtype: string
1938
    @return: instance name
1939

1940
    """
1941
    return self._UnlockedGetInstanceName(inst_uuid)
1942

    
1943
  @_ConfigSync(shared=1)
1944
  def GetInstanceNames(self, inst_uuids):
1945
    """Gets the instance names for the passed list of nodes.
1946

1947
    @param inst_uuids: list of instance UUIDs to get names for
1948
    @type inst_uuids: list of strings
1949
    @rtype: list of strings
1950
    @return: list of instance names
1951

1952
    """
1953
    return self._UnlockedGetInstanceNames(inst_uuids)
1954

    
1955
  @_ConfigSync()
1956
  def SetInstancePrimaryNode(self, inst_uuid, target_node_uuid):
1957
    """Sets the primary node of an existing instance
1958

1959
    @param inst_uuid: instance UUID
1960
    @type inst_uuid: string
1961
    @param target_node_uuid: the new primary node UUID
1962
    @type target_node_uuid: string
1963

1964
    """
1965
    self._UnlockedGetInstanceInfo(inst_uuid).primary_node = target_node_uuid
1966

    
1967
  def _UnlockedGetInstanceNames(self, inst_uuids):
1968
    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1969

    
1970
  def _UnlockedAddNode(self, node, ec_id):
1971
    """Add a node to the configuration.
1972

1973
    @type node: L{objects.Node}
1974
    @param node: a Node instance
1975

1976
    """
1977
    logging.info("Adding node %s to configuration", node.name)
1978

    
1979
    self._EnsureUUID(node, ec_id)
1980

    
1981
    node.serial_no = 1
1982
    node.ctime = node.mtime = time.time()
1983
    self._UnlockedAddNodeToGroup(node.uuid, node.group)
1984
    assert node.uuid in self._ConfigData().nodegroups[node.group].members
1985
    self._ConfigData().nodes[node.uuid] = node
1986
    self._ConfigData().cluster.serial_no += 1
1987

    
1988
  @_ConfigSync()
1989
  def AddNode(self, node, ec_id):
1990
    """Add a node to the configuration.
1991

1992
    @type node: L{objects.Node}
1993
    @param node: a Node instance
1994

1995
    """
1996
    self._UnlockedAddNode(node, ec_id)
1997

    
1998
  @_ConfigSync()
1999
  def RemoveNode(self, node_uuid):
2000
    """Remove a node from the configuration.
2001

2002
    """
2003
    logging.info("Removing node %s from configuration", node_uuid)
2004

    
2005
    if node_uuid not in self._ConfigData().nodes:
2006
      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
2007

    
2008
    self._UnlockedRemoveNodeFromGroup(self._ConfigData().nodes[node_uuid])
2009
    del self._ConfigData().nodes[node_uuid]
2010
    self._ConfigData().cluster.serial_no += 1
2011

    
2012
  def ExpandNodeName(self, short_name):
2013
    """Attempt to expand an incomplete node name into a node UUID.
2014

2015
    """
2016
    # Locking is done in L{ConfigWriter.GetAllNodesInfo}
2017
    all_nodes = self.GetAllNodesInfo().values()
2018
    expanded_name = _MatchNameComponentIgnoreCase(
2019
                      short_name, [node.name for node in all_nodes])
2020

    
2021
    if expanded_name is not None:
2022
      # there has to be exactly one node with that name
2023
      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
2024
      return (node.uuid, node.name)
2025
    else:
2026
      return (None, None)
2027

    
2028
  def _UnlockedGetNodeInfo(self, node_uuid):
2029
    """Get the configuration of a node, as stored in the config.
2030

2031
    This function is for internal use, when the config lock is already
2032
    held.
2033

2034
    @param node_uuid: the node UUID
2035

2036
    @rtype: L{objects.Node}
2037
    @return: the node object
2038

2039
    """
2040
    if node_uuid not in self._ConfigData().nodes:
2041
      return None
2042

    
2043
    return self._ConfigData().nodes[node_uuid]
2044

    
2045
  @_ConfigSync(shared=1)
2046
  def GetNodeInfo(self, node_uuid):
2047
    """Get the configuration of a node, as stored in the config.
2048

2049
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
2050

2051
    @param node_uuid: the node UUID
2052

2053
    @rtype: L{objects.Node}
2054
    @return: the node object
2055

2056
    """
2057
    return self._UnlockedGetNodeInfo(node_uuid)
2058

    
2059
  @_ConfigSync(shared=1)
2060
  def GetNodeInstances(self, node_uuid):
2061
    """Get the instances of a node, as stored in the config.
2062

2063
    @param node_uuid: the node UUID
2064

2065
    @rtype: (list, list)
2066
    @return: a tuple with two lists: the primary and the secondary instances
2067

2068
    """
2069
    pri = []
2070
    sec = []
2071
    for inst in self._ConfigData().instances.values():
2072
      if inst.primary_node == node_uuid:
2073
        pri.append(inst.uuid)
2074
      if node_uuid in inst.secondary_nodes:
2075
        sec.append(inst.uuid)
2076
    return (pri, sec)
2077

    
2078
  @_ConfigSync(shared=1)
2079
  def GetNodeGroupInstances(self, uuid, primary_only=False):
2080
    """Get the instances of a node group.
2081

2082
    @param uuid: Node group UUID
2083
    @param primary_only: Whether to only consider primary nodes
2084
    @rtype: frozenset
2085
    @return: List of instance UUIDs in node group
2086

2087
    """
2088
    if primary_only:
2089
      nodes_fn = lambda inst: [inst.primary_node]
2090
    else:
2091
      nodes_fn = lambda inst: inst.all_nodes
2092

    
2093
    return frozenset(inst.uuid
2094
                     for inst in self._ConfigData().instances.values()
2095
                     for node_uuid in nodes_fn(inst)
2096
                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
2097

    
2098
  def _UnlockedGetHvparamsString(self, hvname):
2099
    """Return the string representation of the list of hyervisor parameters of
2100
    the given hypervisor.
2101

2102
    @see: C{GetHvparams}
2103

2104
    """
2105
    result = ""
2106
    hvparams = self._ConfigData().cluster.hvparams[hvname]
2107
    for key in hvparams:
2108
      result += "%s=%s\n" % (key, hvparams[key])
2109
    return result
2110

    
2111
  @_ConfigSync(shared=1)
2112
  def GetHvparamsString(self, hvname):
2113
    """Return the hypervisor parameters of the given hypervisor.
2114

2115
    @type hvname: string
2116
    @param hvname: name of a hypervisor
2117
    @rtype: string
2118
    @return: string containing key-value-pairs, one pair on each line;
2119
      format: KEY=VALUE
2120

2121
    """
2122
    return self._UnlockedGetHvparamsString(hvname)
2123

    
2124
  def _UnlockedGetNodeList(self):
2125
    """Return the list of nodes which are in the configuration.
2126

2127
    This function is for internal use, when the config lock is already
2128
    held.
2129

2130
    @rtype: list
2131

2132
    """
2133
    return self._ConfigData().nodes.keys()
2134

    
2135
  @_ConfigSync(shared=1)
2136
  def GetNodeList(self):
2137
    """Return the list of nodes which are in the configuration.
2138

2139
    """
2140
    return self._UnlockedGetNodeList()
2141

    
2142
  def _UnlockedGetOnlineNodeList(self):
2143
    """Return the list of nodes which are online.
2144

2145
    """
2146
    all_nodes = [self._UnlockedGetNodeInfo(node)
2147
                 for node in self._UnlockedGetNodeList()]
2148
    return [node.uuid for node in all_nodes if not node.offline]
2149

    
2150
  @_ConfigSync(shared=1)
2151
  def GetOnlineNodeList(self):
2152
    """Return the list of nodes which are online.
2153

2154
    """
2155
    return self._UnlockedGetOnlineNodeList()
2156

    
2157
  @_ConfigSync(shared=1)
2158
  def GetVmCapableNodeList(self):
2159
    """Return the list of nodes which are not vm capable.
2160

2161
    """
2162
    all_nodes = [self._UnlockedGetNodeInfo(node)
2163
                 for node in self._UnlockedGetNodeList()]
2164
    return [node.uuid for node in all_nodes if node.vm_capable]
2165

    
2166
  @_ConfigSync(shared=1)
2167
  def GetNonVmCapableNodeList(self):
2168
    """Return the list of nodes which are not vm capable.
2169

2170
    """
2171
    all_nodes = [self._UnlockedGetNodeInfo(node)
2172
                 for node in self._UnlockedGetNodeList()]
2173
    return [node.uuid for node in all_nodes if not node.vm_capable]
2174

    
2175
  @_ConfigSync(shared=1)
2176
  def GetMultiNodeInfo(self, node_uuids):
2177
    """Get the configuration of multiple nodes.
2178

2179
    @param node_uuids: list of node UUIDs
2180
    @rtype: list
2181
    @return: list of tuples of (node, node_info), where node_info is
2182
        what would GetNodeInfo return for the node, in the original
2183
        order
2184

2185
    """
2186
    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
2187

    
2188
  def _UnlockedGetAllNodesInfo(self):
2189
    """Gets configuration of all nodes.
2190

2191
    @note: See L{GetAllNodesInfo}
2192

2193
    """
2194
    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
2195
                 for node_uuid in self._UnlockedGetNodeList()])
2196

    
2197
  @_ConfigSync(shared=1)
2198
  def GetAllNodesInfo(self):
2199
    """Get the configuration of all nodes.
2200

2201
    @rtype: dict
2202
    @return: dict of (node, node_info), where node_info is what
2203
              would GetNodeInfo return for the node
2204

2205
    """
2206
    return self._UnlockedGetAllNodesInfo()
2207

    
2208
  def _UnlockedGetNodeInfoByName(self, node_name):
2209
    for node in self._UnlockedGetAllNodesInfo().values():
2210
      if node.name == node_name:
2211
        return node
2212
    return None
2213

    
2214
  @_ConfigSync(shared=1)
2215
  def GetNodeInfoByName(self, node_name):
2216
    """Get the L{objects.Node} object for a named node.
2217

2218
    @param node_name: name of the node to get information for
2219
    @type node_name: string
2220
    @return: the corresponding L{objects.Node} instance or None if no
2221
          information is available
2222

2223
    """
2224
    return self._UnlockedGetNodeInfoByName(node_name)
2225

    
2226
  @_ConfigSync(shared=1)
2227
  def GetNodeGroupInfoByName(self, nodegroup_name):
2228
    """Get the L{objects.NodeGroup} object for a named node group.
2229

2230
    @param nodegroup_name: name of the node group to get information for
2231
    @type nodegroup_name: string
2232
    @return: the corresponding L{objects.NodeGroup} instance or None if no
2233
          information is available
2234

2235
    """
2236
    for nodegroup in self._UnlockedGetAllNodeGroupsInfo().values():
2237
      if nodegroup.name == nodegroup_name:
2238
        return nodegroup
2239
    return None
2240

    
2241
  def _UnlockedGetNodeName(self, node_spec):
2242
    if isinstance(node_spec, objects.Node):
2243
      return node_spec.name
2244
    elif isinstance(node_spec, basestring):
2245
      node_info = self._UnlockedGetNodeInfo(node_spec)
2246
      if node_info is None:
2247
        raise errors.OpExecError("Unknown node: %s" % node_spec)
2248
      return node_info.name
2249
    else:
2250
      raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2251

    
2252
  @_ConfigSync(shared=1)
2253
  def GetNodeName(self, node_spec):
2254
    """Gets the node name for the passed node.
2255

2256
    @param node_spec: node to get names for
2257
    @type node_spec: either node UUID or a L{objects.Node} object
2258
    @rtype: string
2259
    @return: node name
2260

2261
    """
2262
    return self._UnlockedGetNodeName(node_spec)
2263

    
2264
  def _UnlockedGetNodeNames(self, node_specs):
2265
    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2266

    
2267
  @_ConfigSync(shared=1)
2268
  def GetNodeNames(self, node_specs):
2269
    """Gets the node names for the passed list of nodes.
2270

2271
    @param node_specs: list of nodes to get names for
2272
    @type node_specs: list of either node UUIDs or L{objects.Node} objects
2273
    @rtype: list of strings
2274
    @return: list of node names
2275

2276
    """
2277
    return self._UnlockedGetNodeNames(node_specs)
2278

    
2279
  @_ConfigSync(shared=1)
2280
  def GetNodeGroupsFromNodes(self, node_uuids):
2281
    """Returns groups for a list of nodes.
2282

2283
    @type node_uuids: list of string
2284
    @param node_uuids: List of node UUIDs
2285
    @rtype: frozenset
2286

2287
    """
2288
    return frozenset(self._UnlockedGetNodeInfo(uuid).group
2289
                     for uuid in node_uuids)
2290

    
2291
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2292
    """Get the number of current and maximum desired and possible candidates.
2293

2294
    @type exceptions: list
2295
    @param exceptions: if passed, list of nodes that should be ignored
2296
    @rtype: tuple
2297
    @return: tuple of (current, desired and possible, possible)
2298

2299
    """
2300
    mc_now = mc_should = mc_max = 0
2301
    for node in self._ConfigData().nodes.values():
2302
      if exceptions and node.uuid in exceptions:
2303
        continue
2304
      if not (node.offline or node.drained) and node.master_capable:
2305
        mc_max += 1
2306
      if node.master_candidate:
2307
        mc_now += 1
2308
    mc_should = min(mc_max, self._ConfigData().cluster.candidate_pool_size)
2309
    return (mc_now, mc_should, mc_max)
2310

    
2311
  @_ConfigSync(shared=1)
2312
  def GetMasterCandidateStats(self, exceptions=None):
2313
    """Get the number of current and maximum possible candidates.
2314

2315
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2316

2317
    @type exceptions: list
2318
    @param exceptions: if passed, list of nodes that should be ignored
2319
    @rtype: tuple
2320
    @return: tuple of (current, max)
2321

2322
    """
2323
    return self._UnlockedGetMasterCandidateStats(exceptions)
2324

    
2325
  @_ConfigSync()
2326
  def MaintainCandidatePool(self, exception_node_uuids):
2327
    """Try to grow the candidate pool to the desired size.
2328

2329
    @type exception_node_uuids: list
2330
    @param exception_node_uuids: if passed, list of nodes that should be ignored
2331
    @rtype: list
2332
    @return: list with the adjusted nodes (L{objects.Node} instances)
2333

2334
    """
2335
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2336
                          exception_node_uuids)
2337
    mod_list = []
2338
    if mc_now < mc_max:
2339
      node_list = self._ConfigData().nodes.keys()
2340
      random.shuffle(node_list)
2341
      for uuid in node_list:
2342
        if mc_now >= mc_max:
2343
          break
2344
        node = self._ConfigData().nodes[uuid]
2345
        if (node.master_candidate or node.offline or node.drained or
2346
            node.uuid in exception_node_uuids or not node.master_capable):
2347
          continue
2348
        mod_list.append(node)
2349
        node.master_candidate = True
2350
        node.serial_no += 1
2351
        mc_now += 1
2352
      if mc_now != mc_max:
2353
        # this should not happen
2354
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
2355
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
2356
      if mod_list:
2357
        self._ConfigData().cluster.serial_no += 1
2358

    
2359
    return mod_list
2360

    
2361
  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2362
    """Add a given node to the specified group.
2363

2364
    """
2365
    if nodegroup_uuid not in self._ConfigData().nodegroups:
2366
      # This can happen if a node group gets deleted between its lookup and
2367
      # when we're adding the first node to it, since we don't keep a lock in
2368
      # the meantime. It's ok though, as we'll fail cleanly if the node group
2369
      # is not found anymore.
2370
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2371
    if node_uuid not in self._ConfigData().nodegroups[nodegroup_uuid].members:
2372
      self._ConfigData().nodegroups[nodegroup_uuid].members.append(node_uuid)
2373

    
2374
  def _UnlockedRemoveNodeFromGroup(self, node):
2375
    """Remove a given node from its group.
2376

2377
    """
2378
    nodegroup = node.group
2379
    if nodegroup not in self._ConfigData().nodegroups:
2380
      logging.warning("Warning: node '%s' has unknown node group '%s'"
2381
                      " (while being removed from it)", node.uuid, nodegroup)
2382
    nodegroup_obj = self._ConfigData().nodegroups[nodegroup]
2383
    if node.uuid not in nodegroup_obj.members:
2384
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
2385
                      " (while being removed from it)", node.uuid, nodegroup)
2386
    else:
2387
      nodegroup_obj.members.remove(node.uuid)
2388

    
2389
  @_ConfigSync()
2390
  def AssignGroupNodes(self, mods):
2391
    """Changes the group of a number of nodes.
2392

2393
    @type mods: list of tuples; (node name, new group UUID)
2394
    @param mods: Node membership modifications
2395

2396
    """
2397
    groups = self._ConfigData().nodegroups
2398
    nodes = self._ConfigData().nodes
2399

    
2400
    resmod = []
2401

    
2402
    # Try to resolve UUIDs first
2403
    for (node_uuid, new_group_uuid) in mods:
2404
      try:
2405
        node = nodes[node_uuid]
2406
      except KeyError:
2407
        raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2408

    
2409
      if node.group == new_group_uuid:
2410
        # Node is being assigned to its current group
2411
        logging.debug("Node '%s' was assigned to its current group (%s)",
2412
                      node_uuid, node.group)
2413
        continue
2414

    
2415
      # Try to find current group of node
2416
      try:
2417
        old_group = groups[node.group]
2418
      except KeyError:
2419
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2420
                                        node.group)
2421

    
2422
      # Try to find new group for node
2423
      try:
2424
        new_group = groups[new_group_uuid]
2425
      except KeyError:
2426
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2427
                                        new_group_uuid)
2428

    
2429
      assert node.uuid in old_group.members, \
2430
        ("Inconsistent configuration: node '%s' not listed in members for its"
2431
         " old group '%s'" % (node.uuid, old_group.uuid))
2432
      assert node.uuid not in new_group.members, \
2433
        ("Inconsistent configuration: node '%s' already listed in members for"
2434
         " its new group '%s'" % (node.uuid, new_group.uuid))
2435

    
2436
      resmod.append((node, old_group, new_group))
2437

    
2438
    # Apply changes
2439
    for (node, old_group, new_group) in resmod:
2440
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2441
        "Assigning to current group is not possible"
2442

    
2443
      node.group = new_group.uuid
2444

    
2445
      # Update members of involved groups
2446
      if node.uuid in old_group.members:
2447
        old_group.members.remove(node.uuid)
2448
      if node.uuid not in new_group.members:
2449
        new_group.members.append(node.uuid)
2450

    
2451
    # Update timestamps and serials (only once per node/group object)
2452
    now = time.time()
2453
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2454
      obj.serial_no += 1
2455
      obj.mtime = now
2456

    
2457
    # Force ssconf update
2458
    self._ConfigData().cluster.serial_no += 1
2459

    
2460
  def _BumpSerialNo(self):
2461
    """Bump up the serial number of the config.
2462

2463
    """
2464
    self._ConfigData().serial_no += 1
2465
    self._ConfigData().mtime = time.time()
2466

    
2467
  def _AllUUIDObjects(self):
2468
    """Returns all objects with uuid attributes.
2469

2470
    """
2471
    return (self._ConfigData().instances.values() +
2472
            self._ConfigData().nodes.values() +
2473
            self._ConfigData().nodegroups.values() +
2474
            self._ConfigData().networks.values() +
2475
            self._AllDisks() +
2476
            self._AllNICs() +
2477
            [self._ConfigData().cluster])
2478

    
2479
  def GetConfigManager(self, shared=False):
2480
    """Returns a ConfigManager, which is suitable to perform a synchronized
2481
    block of configuration operations.
2482

2483
    WARNING: This blocks all other configuration operations, so anything that
2484
    runs inside the block should be very fast, preferably not using any IO.
2485
    """
2486

    
2487
    return ConfigManager(self, shared)
2488

    
2489
  def _AddLockCount(self, count):
2490
    self._lock_count += count
2491
    return self._lock_count
2492

    
2493
  def _LockCount(self):
2494
    return self._lock_count
2495

    
2496
  def _OpenConfig(self, shared):
2497
    """Read the config data from WConfd or disk.
2498

2499
    """
2500
    if self._LockCount() > 0:
2501
      raise errors.ConfigurationError("Configuration lock isn't reentrant")
2502
    self._AddLockCount(1)
2503
    # Read the configuration data. If offline, read the file directly.
2504
    # If online, call WConfd.
2505
    if self._offline:
2506
      raw_data = utils.ReadFile(self._cfg_file)
2507
      try:
2508
        dict_data = serializer.Load(raw_data)
2509
      except Exception, err:
2510
        raise errors.ConfigurationError(err)
2511
      self._cfg_id = utils.GetFileID(path=self._cfg_file)
2512

    
2513
      try:
2514
        data = objects.ConfigData.FromDict(dict_data)
2515
      except Exception, err:
2516
        raise errors.ConfigurationError(err)
2517

    
2518
      # Make sure the configuration has the right version
2519
      _ValidateConfig(data)
2520

    
2521
      if (not hasattr(data, "cluster") or
2522
          not hasattr(data.cluster, "rsahostkeypub")):
2523
        raise errors.ConfigurationError("Incomplete configuration"
2524
                                        " (missing cluster.rsahostkeypub)")
2525

    
2526
      if not data.cluster.master_node in data.nodes:
2527
        msg = ("The configuration denotes node %s as master, but does not"
2528
               " contain information about this node" %
2529
               data.cluster.master_node)
2530
        raise errors.ConfigurationError(msg)
2531

    
2532
      master_info = data.nodes[data.cluster.master_node]
2533
      if master_info.name != self._my_hostname and not self._accept_foreign:
2534
        msg = ("The configuration denotes node %s as master, while my"
2535
               " hostname is %s; opening a foreign configuration is only"
2536
               " possible in accept_foreign mode" %
2537
               (master_info.name, self._my_hostname))
2538
        raise errors.ConfigurationError(msg)
2539

    
2540
      self._SetConfigData(data)
2541

    
2542
      # Upgrade configuration if needed
2543
      self._UpgradeConfig(saveafter=True)
2544
    else:
2545
      # poll until we acquire the lock
2546
      while True:
2547
        dict_data = \
2548
          self._wconfd.LockConfig(self._GetWConfdContext(), bool(shared))
2549
        logging.debug("Received '%s' from WConfd.LockConfig [shared=%s]",
2550
                      str(dict_data), bool(shared))
2551
        if dict_data is not None:
2552
          break
2553
        time.sleep(random.random())
2554

    
2555
      try:
2556
        self._SetConfigData(objects.ConfigData.FromDict(dict_data))
2557
      except Exception, err:
2558
        raise errors.ConfigurationError(err)
2559

    
2560
      # Transitional fix until ConfigWriter is completely rewritten into
2561
      # Haskell
2562
      self._UpgradeConfig()
2563

    
2564
  def _CloseConfig(self, save):
2565
    """Release resources relating the config data.
2566

2567
    """
2568
    try:
2569
      if save:
2570
        self._WriteConfig()
2571
    except Exception, err:
2572
      logging.critical("Can't write the configuration: %s", str(err))
2573
      raise
2574
    finally:
2575
      if not self._offline:
2576
        try:
2577
          self._wconfd.UnlockConfig(self._GetWConfdContext())
2578
        except AttributeError:
2579
          # If the configuration hasn't been initialized yet, just ignore it.
2580
          pass
2581
        logging.debug("Configuration in WConfd unlocked")
2582
      self._AddLockCount(-1)
2583

    
2584
  # TODO: To WConfd
2585
  def _UpgradeConfig(self, saveafter=False):
2586
    """Run any upgrade steps.
2587

2588
    This method performs both in-object upgrades and also update some data
2589
    elements that need uniqueness across the whole configuration or interact
2590
    with other objects.
2591

2592
    @warning: if 'saveafter' is 'True', this function will call
2593
        L{_WriteConfig()} so it needs to be called only from a
2594
        "safe" place.
2595

2596
    """
2597
    # Keep a copy of the persistent part of _config_data to check for changes
2598
    # Serialization doesn't guarantee order in dictionaries
2599
    oldconf = copy.deepcopy(self._ConfigData().ToDict())
2600

    
2601
    # In-object upgrades
2602
    self._ConfigData().UpgradeConfig()
2603

    
2604
    for item in self._AllUUIDObjects():
2605
      if item.uuid is None:
2606
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2607
    if not self._ConfigData().nodegroups:
2608
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2609
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2610
                                            members=[])
2611
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2612
    for node in self._ConfigData().nodes.values():
2613
      if not node.group:
2614
        node.group = self._UnlockedLookupNodeGroup(None)
2615
      # This is technically *not* an upgrade, but needs to be done both when
2616
      # nodegroups are being added, and upon normally loading the config,
2617
      # because the members list of a node group is discarded upon
2618
      # serializing/deserializing the object.
2619
      self._UnlockedAddNodeToGroup(node.uuid, node.group)
2620

    
2621
    modified = (oldconf != self._ConfigData().ToDict())
2622
    if modified and saveafter:
2623
      self._WriteConfig()
2624
      self._UnlockedDropECReservations(_UPGRADE_CONFIG_JID)
2625
    else:
2626
      config_errors = self._UnlockedVerifyConfig()
2627
      if config_errors:
2628
        errmsg = ("Loaded configuration data is not consistent: %s" %
2629
                  (utils.CommaJoin(config_errors)))
2630
        logging.critical(errmsg)
2631

    
2632
  def _WriteConfig(self, destination=None, feedback_fn=None):
2633
    """Write the configuration data to persistent storage.
2634

2635
    """
2636
    assert feedback_fn is None or callable(feedback_fn)
2637

    
2638
    # Warn on config errors, but don't abort the save - the
2639
    # configuration has already been modified, and we can't revert;
2640
    # the best we can do is to warn the user and save as is, leaving
2641
    # recovery to the user
2642
    config_errors = self._UnlockedVerifyConfig()
2643
    if config_errors:
2644
      errmsg = ("Configuration data is not consistent: %s" %
2645
                (utils.CommaJoin(config_errors)))
2646
      logging.critical(errmsg)
2647
      if feedback_fn:
2648
        feedback_fn(errmsg)
2649

    
2650
    if destination is None:
2651
      destination = self._cfg_file
2652

    
2653
    self._BumpSerialNo()
2654
    # Save the configuration data. If offline, write the file directly.
2655
    # If online, call WConfd.
2656
    if self._offline:
2657
      txt = serializer.DumpJson(
2658
        self._ConfigData().ToDict(_with_private=True),
2659
        private_encoder=serializer.EncodeWithPrivateFields
2660
      )
2661

    
2662
      getents = self._getents()
2663
      try:
2664
        fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2665
                                 close=False, gid=getents.confd_gid, mode=0640)
2666
      except errors.LockError:
2667
        raise errors.ConfigurationError("The configuration file has been"
2668
                                        " modified since the last write, cannot"
2669
                                        " update")
2670
      try:
2671
        self._cfg_id = utils.GetFileID(fd=fd)
2672
      finally:
2673
        os.close(fd)
2674
    else:
2675
      try:
2676
        self._wconfd.WriteConfig(self._GetWConfdContext(),
2677
                                 self._ConfigData().ToDict())
2678
      except errors.LockError:
2679
        raise errors.ConfigurationError("The configuration file has been"
2680
                                        " modified since the last write, cannot"
2681
                                        " update")
2682

    
2683
    self.write_count += 1
2684

    
2685
  def _GetAllHvparamsStrings(self, hypervisors):
2686
    """Get the hvparams of all given hypervisors from the config.
2687

2688
    @type hypervisors: list of string
2689
    @param hypervisors: list of hypervisor names
2690
    @rtype: dict of strings
2691
    @returns: dictionary mapping the hypervisor name to a string representation
2692
      of the hypervisor's hvparams
2693

2694
    """
2695
    hvparams = {}
2696
    for hv in hypervisors:
2697
      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2698
    return hvparams
2699

    
2700
  @staticmethod
2701
  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2702
    """Extends the ssconf_values dictionary by hvparams.
2703

2704
    @type ssconf_values: dict of strings
2705
    @param ssconf_values: dictionary mapping ssconf_keys to strings
2706
      representing the content of ssconf files
2707
    @type all_hvparams: dict of strings
2708
    @param all_hvparams: dictionary mapping hypervisor names to a string
2709
      representation of their hvparams
2710
    @rtype: same as ssconf_values
2711
    @returns: the ssconf_values dictionary extended by hvparams
2712

2713
    """
2714
    for hv in all_hvparams:
2715
      ssconf_key = constants.SS_HVPARAMS_PREF + hv
2716
      ssconf_values[ssconf_key] = all_hvparams[hv]
2717
    return ssconf_values
2718

    
2719
  def _UnlockedGetSsconfValues(self):
2720
    """Return the values needed by ssconf.
2721

2722
    @rtype: dict
2723
    @return: a dictionary with keys the ssconf names and values their
2724
        associated value
2725

2726
    """
2727
    fn = "\n".join
2728
    instance_names = utils.NiceSort(
2729
                       [inst.name for inst in
2730
                        self._UnlockedGetAllInstancesInfo().values()])
2731
    node_infos = self._UnlockedGetAllNodesInfo().values()
2732
    node_names = [node.name for node in node_infos]
2733
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2734
                    for ninfo in node_infos]
2735
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2736
                    for ninfo in node_infos]
2737

    
2738
    instance_data = fn(instance_names)
2739
    off_data = fn(node.name for node in node_infos if node.offline)
2740
    on_data = fn(node.name for node in node_infos if not node.offline)
2741
    mc_data = fn(node.name for node in node_infos if node.master_candidate)
2742
    mc_ips_data = fn(node.primary_ip for node in node_infos
2743
                     if node.master_candidate)
2744
    node_data = fn(node_names)
2745
    node_pri_ips_data = fn(node_pri_ips)
2746
    node_snd_ips_data = fn(node_snd_ips)
2747

    
2748
    cluster = self._ConfigData().cluster
2749
    cluster_tags = fn(cluster.GetTags())
2750

    
2751
    master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert)
2752
                                 for mc_uuid, mc_cert
2753
                                 in cluster.candidate_certs.items())
2754

    
2755
    hypervisor_list = fn(cluster.enabled_hypervisors)
2756
    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2757

    
2758
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2759

    
2760
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2761
                  self._ConfigData().nodegroups.values()]
2762
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2763
    networks = ["%s %s" % (net.uuid, net.name) for net in
2764
                self._ConfigData().networks.values()]
2765
    networks_data = fn(utils.NiceSort(networks))
2766

    
2767
    ssconf_values = {
2768
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
2769
      constants.SS_CLUSTER_TAGS: cluster_tags,
2770
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2771
      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2772
      constants.SS_GLUSTER_STORAGE_DIR: cluster.gluster_storage_dir,
2773
      constants.SS_MASTER_CANDIDATES: mc_data,
2774
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2775
      constants.SS_MASTER_CANDIDATES_CERTS: master_candidates_certs,
2776
      constants.SS_MASTER_IP: cluster.master_ip,
2777
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
2778
      constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2779
      constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2780
      constants.SS_NODE_LIST: node_data,
2781
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2782
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2783
      constants.SS_OFFLINE_NODES: off_data,
2784
      constants.SS_ONLINE_NODES: on_data,
2785
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2786
      constants.SS_INSTANCE_LIST: instance_data,
2787
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2788
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
2789
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2790
      constants.SS_UID_POOL: uid_pool,
2791
      constants.SS_NODEGROUPS: nodegroups_data,
2792
      constants.SS_NETWORKS: networks_data,
2793
      }
2794
    ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2795
                                                     all_hvparams)
2796
    bad_values = [(k, v) for k, v in ssconf_values.items()
2797
                  if not isinstance(v, (str, basestring))]
2798
    if bad_values:
2799
      err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2800
      raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2801
                                      " values: %s" % err)
2802
    return ssconf_values
2803

    
2804
  @_ConfigSync(shared=1)
2805
  def GetSsconfValues(self):
2806
    """Wrapper using lock around _UnlockedGetSsconf().
2807

2808
    """
2809
    return self._UnlockedGetSsconfValues()
2810

    
2811
  @_ConfigSync(shared=1)
2812
  def GetVGName(self):
2813
    """Return the volume group name.
2814

2815
    """
2816
    return self._ConfigData().cluster.volume_group_name
2817

    
2818
  @_ConfigSync()
2819
  def SetVGName(self, vg_name):
2820
    """Set the volume group name.
2821

2822
    """
2823
    self._ConfigData().cluster.volume_group_name = vg_name
2824
    self._ConfigData().cluster.serial_no += 1
2825

    
2826
  @_ConfigSync(shared=1)
2827
  def GetDRBDHelper(self):
2828
    """Return DRBD usermode helper.
2829

2830
    """
2831
    return self._ConfigData().cluster.drbd_usermode_helper
2832

    
2833
  @_ConfigSync()
2834
  def SetDRBDHelper(self, drbd_helper):
2835
    """Set DRBD usermode helper.
2836

2837
    """
2838
    self._ConfigData().cluster.drbd_usermode_helper = drbd_helper
2839
    self._ConfigData().cluster.serial_no += 1
2840

    
2841
  @_ConfigSync(shared=1)
2842
  def GetMACPrefix(self):
2843
    """Return the mac prefix.
2844

2845
    """
2846
    return self._ConfigData().cluster.mac_prefix
2847

    
2848
  @_ConfigSync(shared=1)
2849
  def GetClusterInfo(self):
2850
    """Returns information about the cluster
2851

2852
    @rtype: L{objects.Cluster}
2853
    @return: the cluster object
2854

2855
    """
2856
    return self._ConfigData().cluster
2857

    
2858
  @_ConfigSync(shared=1)
2859
  def HasAnyDiskOfType(self, dev_type):
2860
    """Check if in there is at disk of the given type in the configuration.
2861

2862
    """
2863
    return self._ConfigData().HasAnyDiskOfType(dev_type)
2864

    
2865
  @_ConfigSync()
2866
  def Update(self, target, feedback_fn, ec_id=None):
2867
    """Notify function to be called after updates.
2868

2869
    This function must be called when an object (as returned by
2870
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2871
    caller wants the modifications saved to the backing store. Note
2872
    that all modified objects will be saved, but the target argument
2873
    is the one the caller wants to ensure that it's saved.
2874

2875
    @param target: an instance of either L{objects.Cluster},
2876
        L{objects.Node} or L{objects.Instance} which is existing in
2877
        the cluster
2878
    @param feedback_fn: Callable feedback function
2879

2880
    """
2881
    if self._ConfigData() is None:
2882
      raise errors.ProgrammerError("Configuration file not read,"
2883
                                   " cannot save.")
2884

    
2885
    def check_serial(target, current):
2886
      if current is None:
2887
        raise errors.ConfigurationError("Configuration object unknown")
2888
      elif current.serial_no != target.serial_no:
2889
        raise errors.ConfigurationError("Configuration object updated since"
2890
                                        " it has been read: %d != %d",
2891
                                        current.serial_no, target.serial_no)
2892

    
2893
    def replace_in(target, tdict):
2894
      check_serial(target, tdict.get(target.uuid))
2895
      tdict[target.uuid] = target
2896

    
2897
    update_serial = False
2898
    if isinstance(target, objects.Cluster):
2899
      check_serial(target, self._ConfigData().cluster)
2900
      self._ConfigData().cluster = target
2901
    elif isinstance(target, objects.Node):
2902
      replace_in(target, self._ConfigData().nodes)
2903
      update_serial = True
2904
    elif isinstance(target, objects.Instance):
2905
      replace_in(target, self._ConfigData().instances)
2906
    elif isinstance(target, objects.NodeGroup):
2907
      replace_in(target, self._ConfigData().nodegroups)
2908
    elif isinstance(target, objects.Network):
2909
      replace_in(target, self._ConfigData().networks)
2910
    else:
2911
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2912
                                   " ConfigWriter.Update" % type(target))
2913
    target.serial_no += 1
2914
    target.mtime = now = time.time()
2915

    
2916
    if update_serial:
2917
      # for node updates, we need to increase the cluster serial too
2918
      self._ConfigData().cluster.serial_no += 1
2919
      self._ConfigData().cluster.mtime = now
2920

    
2921
    if isinstance(target, objects.Instance):
2922
      self._UnlockedReleaseDRBDMinors(target.uuid)
2923

    
2924
    if ec_id is not None:
2925
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2926
      self._UnlockedCommitTemporaryIps(ec_id)
2927

    
2928
    self._WriteConfig(feedback_fn=feedback_fn)
2929

    
2930
  def _UnlockedDropECReservations(self, ec_id):
2931
    """Drop per-execution-context reservations
2932

2933
    """
2934
    for rm in self._all_rms:
2935
      rm.DropECReservations(ec_id)
2936

    
2937
  @_ConfigSync()
2938
  def DropECReservations(self, ec_id):
2939
    self._UnlockedDropECReservations(ec_id)
2940

    
2941
  @_ConfigSync(shared=1)
2942
  def GetAllNetworksInfo(self):
2943
    """Get configuration info of all the networks.
2944

2945
    """
2946
    return dict(self._ConfigData().networks)
2947

    
2948
  def _UnlockedGetNetworkList(self):
2949
    """Get the list of networks.
2950

2951
    This function is for internal use, when the config lock is already held.
2952

2953
    """
2954
    return self._ConfigData().networks.keys()
2955

    
2956
  @_ConfigSync(shared=1)
2957
  def GetNetworkList(self):
2958
    """Get the list of networks.
2959

2960
    @return: array of networks, ex. ["main", "vlan100", "200]
2961

2962
    """
2963
    return self._UnlockedGetNetworkList()
2964

    
2965
  @_ConfigSync(shared=1)
2966
  def GetNetworkNames(self):
2967
    """Get a list of network names
2968

2969
    """
2970
    names = [net.name
2971
             for net in self._ConfigData().networks.values()]
2972
    return names
2973

    
2974
  def _UnlockedGetNetwork(self, uuid):
2975
    """Returns information about a network.
2976

2977
    This function is for internal use, when the config lock is already held.
2978

2979
    """
2980
    if uuid not in self._ConfigData().networks:
2981
      return None
2982

    
2983
    return self._ConfigData().networks[uuid]
2984

    
2985
  @_ConfigSync(shared=1)
2986
  def GetNetwork(self, uuid):
2987
    """Returns information about a network.
2988

2989
    It takes the information from the configuration file.
2990

2991
    @param uuid: UUID of the network
2992

2993
    @rtype: L{objects.Network}
2994
    @return: the network object
2995

2996
    """
2997
    return self._UnlockedGetNetwork(uuid)
2998

    
2999
  @_ConfigSync()
3000
  def AddNetwork(self, net, ec_id, check_uuid=True):
3001
    """Add a network to the configuration.
3002

3003
    @type net: L{objects.Network}
3004
    @param net: the Network object to add
3005
    @type ec_id: string
3006
    @param ec_id: unique id for the job to use when creating a missing UUID
3007

3008
    """
3009
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
3010

    
3011
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
3012
    """Add a network to the configuration.
3013

3014
    """
3015
    logging.info("Adding network %s to configuration", net.name)
3016

    
3017
    if check_uuid:
3018
      self._EnsureUUID(net, ec_id)
3019

    
3020
    net.serial_no = 1
3021
    net.ctime = net.mtime = time.time()
3022
    self._ConfigData().networks[net.uuid] = net
3023
    self._ConfigData().cluster.serial_no += 1
3024

    
3025
  def _UnlockedLookupNetwork(self, target):
3026
    """Lookup a network's UUID.
3027

3028
    @type target: string
3029
    @param target: network name or UUID
3030
    @rtype: string
3031
    @return: network UUID
3032
    @raises errors.OpPrereqError: when the target network cannot be found
3033

3034
    """
3035
    if target is None:
3036
      return None
3037
    if target in self._ConfigData().networks:
3038
      return target
3039
    for net in self._ConfigData().networks.values():
3040
      if net.name == target:
3041
        return net.uuid
3042
    raise errors.OpPrereqError("Network '%s' not found" % target,
3043
                               errors.ECODE_NOENT)
3044

    
3045
  @_ConfigSync(shared=1)
3046
  def LookupNetwork(self, target):
3047
    """Lookup a network's UUID.
3048

3049
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
3050

3051
    @type target: string
3052
    @param target: network name or UUID
3053
    @rtype: string
3054
    @return: network UUID
3055

3056
    """
3057
    return self._UnlockedLookupNetwork(target)
3058

    
3059
  @_ConfigSync()
3060
  def RemoveNetwork(self, network_uuid):
3061
    """Remove a network from the configuration.
3062

3063
    @type network_uuid: string
3064
    @param network_uuid: the UUID of the network to remove
3065

3066
    """
3067
    logging.info("Removing network %s from configuration", network_uuid)
3068

    
3069
    if network_uuid not in self._ConfigData().networks:
3070
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
3071

    
3072
    del self._ConfigData().networks[network_uuid]
3073
    self._ConfigData().cluster.serial_no += 1
3074

    
3075
  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
3076
    """Get the netparams (mode, link) of a network.
3077

3078
    Get a network's netparams for a given node.
3079

3080
    @type net_uuid: string
3081
    @param net_uuid: network uuid
3082
    @type node_uuid: string
3083
    @param node_uuid: node UUID
3084
    @rtype: dict or None
3085
    @return: netparams
3086

3087
    """
3088
    node_info = self._UnlockedGetNodeInfo(node_uuid)
3089
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
3090
    netparams = nodegroup_info.networks.get(net_uuid, None)
3091

    
3092
    return netparams
3093

    
3094
  @_ConfigSync(shared=1)
3095
  def GetGroupNetParams(self, net_uuid, node_uuid):
3096
    """Locking wrapper of _UnlockedGetGroupNetParams()
3097

3098
    """
3099
    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
3100

    
3101
  @_ConfigSync(shared=1)
3102
  def CheckIPInNodeGroup(self, ip, node_uuid):
3103
    """Check IP uniqueness in nodegroup.
3104

3105
    Check networks that are connected in the node's node group
3106
    if ip is contained in any of them. Used when creating/adding
3107
    a NIC to ensure uniqueness among nodegroups.
3108

3109
    @type ip: string
3110
    @param ip: ip address
3111
    @type node_uuid: string
3112
    @param node_uuid: node UUID
3113
    @rtype: (string, dict) or (None, None)
3114
    @return: (network name, netparams)
3115

3116
    """
3117
    if ip is None:
3118
      return (None, None)
3119
    node_info = self._UnlockedGetNodeInfo(node_uuid)
3120
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
3121
    for net_uuid in nodegroup_info.networks.keys():
3122
      net_info = self._UnlockedGetNetwork(net_uuid)
3123
      pool = network.AddressPool(net_info)
3124
      if pool.Contains(ip):
3125
        return (net_info.name, nodegroup_info.networks[net_uuid])
3126

    
3127
    return (None, None)
3128

    
3129
  @_ConfigSync(shared=1)
3130
  def GetCandidateCerts(self):
3131
    """Returns the candidate certificate map.
3132

3133
    """
3134
    return self._ConfigData().cluster.candidate_certs
3135

    
3136
  @_ConfigSync()
3137
  def AddNodeToCandidateCerts(self, node_uuid, cert_digest,
3138
                              info_fn=logging.info, warn_fn=logging.warn):
3139
    """Adds an entry to the candidate certificate map.
3140

3141
    @type node_uuid: string
3142
    @param node_uuid: the node's UUID
3143
    @type cert_digest: string
3144
    @param cert_digest: the digest of the node's client SSL certificate
3145
    @type info_fn: function
3146
    @param info_fn: logging function for information messages
3147
    @type warn_fn: function
3148
    @param warn_fn: logging function for warning messages
3149

3150
    """
3151
    cluster = self._ConfigData().cluster
3152
    if node_uuid in cluster.candidate_certs:
3153
      old_cert_digest = cluster.candidate_certs[node_uuid]
3154
      if old_cert_digest == cert_digest:
3155
        if info_fn is not None:
3156
          info_fn("Certificate digest for node %s already in config."
3157
                  "Not doing anything." % node_uuid)
3158
        return
3159
      else:
3160
        if warn_fn is not None:
3161
          warn_fn("Overriding differing certificate digest for node %s"
3162
                  % node_uuid)
3163
    cluster.candidate_certs[node_uuid] = cert_digest
3164

    
3165
  @_ConfigSync()
3166
  def RemoveNodeFromCandidateCerts(self, node_uuid,
3167
                                   warn_fn=logging.warn):
3168
    """Removes the entry of the given node in the certificate map.
3169

3170
    @type node_uuid: string
3171
    @param node_uuid: the node's UUID
3172
    @type warn_fn: function
3173
    @param warn_fn: logging function for warning messages
3174

3175
    """
3176
    cluster = self._ConfigData().cluster
3177
    if node_uuid not in cluster.candidate_certs:
3178
      if warn_fn is not None:
3179
        warn_fn("Cannot remove certifcate for node %s, because it's not"
3180
                " in the candidate map." % node_uuid)
3181
      return
3182
    del cluster.candidate_certs[node_uuid]