config.py 111 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
#
Iustin Pop's avatar
Iustin Pop committed
2
3
#

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


"""Configuration management for Ganeti

24
This module provides the interface to the Ganeti cluster configuration.
Iustin Pop's avatar
Iustin Pop committed
25

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

29
30
Currently, the data storage format is JSON. YAML was slow and consuming too
much memory.
Iustin Pop's avatar
Iustin Pop committed
31
32
33

"""

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

37
import copy
Iustin Pop's avatar
Iustin Pop committed
38
39
import os
import random
40
import logging
41
import time
42
import threading
43
import itertools
Iustin Pop's avatar
Iustin Pop committed
44
45
46
47

from ganeti import errors
from ganeti import utils
from ganeti import constants
48
import ganeti.wconfd as wc
Iustin Pop's avatar
Iustin Pop committed
49
from ganeti import objects
50
from ganeti import serializer
Balazs Lecz's avatar
Balazs Lecz committed
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
59
60
61
62
63
64
65
66
67
68
69
70
71
def GetWConfdContext(ec_id, livelock):
  """Prepare a context for communication with WConfd.

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

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

  """
72
73
  if ec_id is None:
    return (threading.current_thread().getName(),
74
            livelock.GetPath(), os.getpid())
75
76
  else:
    return (ec_id,
77
            livelock.GetPath(), os.getpid())
78
79


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
def GetConfig(ec_id, livelock, **kwargs):
  """A utility function for constructing instances of ConfigWriter.

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

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

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


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

  """
  def wrap(fn):
    def sync_function(*args, **kwargs):
      with args[0].GetConfigManager(shared):
        logging.debug("ConfigWriter.%s(%s, %s)",
                      fn.__name__, str(args), str(kwargs))
        result = fn(*args, **kwargs)
Klaus Aehlig's avatar
Klaus Aehlig committed
110
        logging.debug("ConfigWriter.%s(...) returned", fn.__name__)
111
112
113
114
115
116
117
118
        return result
    return sync_function
  return wrap

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


Michael Hanselmann's avatar
Michael Hanselmann committed
119
def _ValidateConfig(data):
120
  """Verifies that a configuration dict looks valid.
Iustin Pop's avatar
Iustin Pop committed
121
122
123
124
125
126
127

  This only verifies the version of the configuration.

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

  """
128
129
130
  if data['version'] != constants.CONFIG_VERSION:
    raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION,
                                       data['version'])
Iustin Pop's avatar
Iustin Pop committed
131

132

Guido Trotter's avatar
Guido Trotter committed
133
134
135
136
137
138
139
140
141
142
143
class TemporaryReservationManager:
  """A temporary resource reservation manager.

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

  """
  def __init__(self):
    self._ec_reserved = {}

  def Reserved(self, resource):
144
    for holder_reserved in self._ec_reserved.values():
Guido Trotter's avatar
Guido Trotter committed
145
146
147
148
149
150
      if resource in holder_reserved:
        return True
    return False

  def Reserve(self, ec_id, resource):
    if self.Reserved(resource):
151
152
      raise errors.ReservationError("Duplicate reservation for resource '%s'"
                                    % str(resource))
Guido Trotter's avatar
Guido Trotter committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
    if ec_id not in self._ec_reserved:
      self._ec_reserved[ec_id] = set([resource])
    else:
      self._ec_reserved[ec_id].add(resource)

  def DropECReservations(self, ec_id):
    if ec_id in self._ec_reserved:
      del self._ec_reserved[ec_id]

  def GetReserved(self):
    all_reserved = set()
    for holder_reserved in self._ec_reserved.values():
      all_reserved.update(holder_reserved)
    return all_reserved

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

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

Guido Trotter's avatar
Guido Trotter committed
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
  def Generate(self, existing, generate_one_fn, ec_id):
    """Generate a new resource of this type

    """
    assert callable(generate_one_fn)

    all_elems = self.GetReserved()
    all_elems.update(existing)
    retries = 64
    while retries > 0:
      new_resource = generate_one_fn()
      if new_resource is not None and new_resource not in all_elems:
        break
    else:
      raise errors.ConfigurationError("Not able generate new resource"
                                      " (last tried: %s)" % new_resource)
    self.Reserve(ec_id, new_resource)
    return new_resource


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

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


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def _CheckInstanceDiskIvNames(disks):
  """Checks if instance's disks' C{iv_name} attributes are in order.

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

  """
  result = []

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

  return result

225

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
class ConfigManager(object):
  """Locks the configuration and exposes it to be read or modified.

  """
  def __init__(self, config_writer, shared=False):
    assert isinstance(config_writer, ConfigWriter), \
           "invalid argument: Not a ConfigWriter"
    self._config_writer = config_writer
    self._shared = shared

  def __enter__(self):
    try:
      self._config_writer._OpenConfig(self._shared) # pylint: disable=W0212
    except Exception:
      logging.debug("Opening configuration failed")
      try:
        self._config_writer._CloseConfig(False) # pylint: disable=W0212
      except Exception: # pylint: disable=W0703
        logging.debug("Closing configuration failed as well")
      raise

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


257
258
259
260
261
262
263
264
265
266
def _UpdateIvNames(base_idx, disks):
  """Update the C{iv_name} attribute of disks.

  @type disks: list of L{objects.Disk}

  """
  for (idx, disk) in enumerate(disks):
    disk.iv_name = "disk/%s" % (base_idx + idx)


267
class ConfigWriter(object):
268
  """The interface to the cluster configuration.
Iustin Pop's avatar
Iustin Pop committed
269

270
271
272
  WARNING: The class is no longer thread-safe!
  Each thread must construct a separate instance.

273
274
275
  @ivar _temporary_lvs: reservation manager for temporary LVs
  @ivar _all_rms: a list of all temporary reservation managers

276
  """
277
  def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
278
               accept_foreign=False, wconfdcontext=None, wconfd=None):
279
    self.write_count = 0
Iustin Pop's avatar
Iustin Pop committed
280
    self._config_data = None
281
    self._SetConfigData(None)
Iustin Pop's avatar
Iustin Pop committed
282
283
    self._offline = offline
    if cfg_file is None:
284
      self._cfg_file = pathutils.CLUSTER_CONF_FILE
Iustin Pop's avatar
Iustin Pop committed
285
286
    else:
      self._cfg_file = cfg_file
287
    self._getents = _getents
288
    self._temporary_ids = TemporaryReservationManager()
289
    self._temporary_drbds = {}
290
    self._temporary_macs = TemporaryReservationManager()
291
    self._temporary_secrets = TemporaryReservationManager()
292
    self._temporary_lvs = TemporaryReservationManager()
293
    self._temporary_ips = TemporaryReservationManager()
294
    self._all_rms = [self._temporary_ids, self._temporary_macs,
295
296
                     self._temporary_secrets, self._temporary_lvs,
                     self._temporary_ips]
297
298
    # Note: in order to prevent errors when resolving our name later,
    # we compute it here once and reuse it; it's
299
300
    # better to raise an error before starting to modify the config
    # file than after it was modified
301
    self._my_hostname = netutils.Hostname.GetSysName()
302
    self._cfg_id = None
303
304
305
306
    self._wconfdcontext = wconfdcontext
    self._wconfd = wconfd
    self._accept_foreign = accept_foreign
    self._lock_count = 0
307
    self._lock_current_shared = None
Iustin Pop's avatar
Iustin Pop committed
308

309
310
311
  def _ConfigData(self):
    return self._config_data

312
313
314
315
316
317
  def _SetConfigData(self, cfg):
    self._config_data = cfg

  def _GetWConfdContext(self):
    return self._wconfdcontext

Iustin Pop's avatar
Iustin Pop committed
318
319
320
321
322
323
  # this method needs to be static, so that we can call it on the class
  @staticmethod
  def IsCluster():
    """Check if the cluster is configured.

    """
324
    return os.path.exists(pathutils.CLUSTER_CONF_FILE)
Iustin Pop's avatar
Iustin Pop committed
325

326
  @_ConfigSync(shared=1)
327
328
329
  def GetNdParams(self, node):
    """Get the node params populated with cluster defaults.

330
    @type node: L{objects.Node}
331
332
333
334
335
    @param node: The node we want to know the params for
    @return: A dict with the filled in node params

    """
    nodegroup = self._UnlockedGetNodeGroup(node.group)
336
    return self._ConfigData().cluster.FillND(node, nodegroup)
337

338
  @_ConfigSync(shared=1)
339
340
341
342
343
344
345
346
  def GetNdGroupParams(self, nodegroup):
    """Get the node groups params populated with cluster defaults.

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

    """
347
    return self._ConfigData().cluster.FillNDGroup(nodegroup)
348

349
  @_ConfigSync(shared=1)
350
351
352
353
354
355
356
357
358
359
  def GetInstanceDiskParams(self, instance):
    """Get the disk params populated with inherit chain.

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

    """
    node = self._UnlockedGetNodeInfo(instance.primary_node)
    nodegroup = self._UnlockedGetNodeGroup(node.group)
360
361
    return self._UnlockedGetGroupDiskParams(nodegroup)

362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
  def _UnlockedGetInstanceDisks(self, inst_uuid):
    """Return the disks' info for the given instance

    @type inst_uuid: string
    @param inst_uuid: The UUID of the instance we want to know the disks for

    @rtype: List of L{objects.Disk}
    @return: A list with all the disks' info

    """
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
    if instance is None:
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)

    return [self._UnlockedGetDiskInfo(disk_uuid)
            for disk_uuid in instance.disks]

  @_ConfigSync(shared=1)
  def GetInstanceDisks(self, inst_uuid):
    """Return the disks' info for the given instance

    This is a simple wrapper over L{_UnlockedGetInstanceDisks}.

    """
    return self._UnlockedGetInstanceDisks(inst_uuid)

388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
  def _UnlockedAddDisk(self, disk):
    """Add a disk to the config.

    @type disk: L{objects.Disk}
    @param disk: The disk object

    """
    if not isinstance(disk, objects.Disk):
      raise errors.ProgrammerError("Invalid type passed to _UnlockedAddDisk")

    logging.info("Adding disk %s to configuration", disk.uuid)

    self._CheckUniqueUUID(disk, include_temporary=False)
    disk.serial_no = 1
    disk.ctime = disk.mtime = time.time()
    disk.UpgradeConfig()
    self._ConfigData().disks[disk.uuid] = disk
    self._ConfigData().cluster.serial_no += 1

  def _UnlockedAttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None):
    """Attach a disk to an instance.

    @type inst_uuid: string
    @param inst_uuid: The UUID of the instance object
    @type disk_uuid: string
    @param disk_uuid: The UUID of the disk object
    @type idx: int
    @param idx: the index of the newly attached disk; if not
      passed, the disk will be attached as the last one.

    """
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
    if instance is None:
      raise errors.ConfigurationError("Instance %s doesn't exist"
                                      % inst_uuid)
    if disk_uuid not in self._ConfigData().disks:
      raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)

    if idx is None:
      idx = len(instance.disks)
    else:
      if idx < 0:
        raise IndexError("Not accepting negative indices other than -1")
      elif idx > len(instance.disks):
        raise IndexError("Got disk index %s, but there are only %s" %
                         (idx, len(instance.disks)))

    # Disk must not be attached anywhere else
    for inst in self._ConfigData().instances.values():
      if disk_uuid in inst.disks:
        raise errors.ReservationError("Disk %s already attached to instance %s"
                                      % (disk_uuid, inst.name))

    instance.disks.insert(idx, disk_uuid)
    instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
    _UpdateIvNames(idx, instance_disks[idx:])
    instance.serial_no += 1
    instance.mtime = time.time()

  @_ConfigSync()
  def AddInstanceDisk(self, inst_uuid, disk, idx=None):
    """Add a disk to the config and attach it to instance.

    This is a simple wrapper over L{_UnlockedAddDisk} and
    L{_UnlockedAttachInstanceDisk}.

    """
    self._UnlockedAddDisk(disk)
    self._UnlockedAttachInstanceDisk(inst_uuid, disk.uuid, idx)

458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
  def _UnlockedDetachInstanceDisk(self, inst_uuid, disk_uuid):
    """Detach a disk from an instance.

    @type inst_uuid: string
    @param inst_uuid: The UUID of the instance object
    @type disk_uuid: string
    @param disk_uuid: The UUID of the disk object

    """
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
    if instance is None:
      raise errors.ConfigurationError("Instance %s doesn't exist"
                                      % inst_uuid)
    if disk_uuid not in self._ConfigData().disks:
      raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)

    # Check if disk is attached to the instance
    if disk_uuid not in instance.disks:
      raise errors.ProgrammerError("Disk %s is not attached to an instance"
                                   % disk_uuid)

    idx = instance.disks.index(disk_uuid)
    instance.disks.remove(disk_uuid)
    instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
    _UpdateIvNames(idx, instance_disks[idx:])
    instance.serial_no += 1
    instance.mtime = time.time()

  def _UnlockedRemoveDisk(self, disk_uuid):
    """Remove the disk from the configuration.

    @type disk_uuid: string
    @param disk_uuid: The UUID of the disk object

    """
    if disk_uuid not in self._ConfigData().disks:
      raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)

    # Disk must not be attached anywhere
    for inst in self._ConfigData().instances.values():
      if disk_uuid in inst.disks:
        raise errors.ReservationError("Cannot remove disk %s. Disk is"
                                      " attached to instance %s"
                                      % (disk_uuid, inst.name))

    # Remove disk from config file
    del self._ConfigData().disks[disk_uuid]
    self._ConfigData().cluster.serial_no += 1

  @_ConfigSync()
  def RemoveInstanceDisk(self, inst_uuid, disk_uuid):
    """Detach a disk from an instance and remove it from the config.

    This is a simple wrapper over L{_UnlockedDetachInstanceDisk} and
    L{_UnlockedRemoveDisk}.

    """
    self._UnlockedDetachInstanceDisk(inst_uuid, disk_uuid)
    self._UnlockedRemoveDisk(disk_uuid)

518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
  def _UnlockedGetDiskInfo(self, disk_uuid):
    """Returns information about a disk.

    It takes the information from the configuration file.

    @param disk_uuid: UUID of the disk

    @rtype: L{objects.Disk}
    @return: the disk object

    """
    if disk_uuid not in self._ConfigData().disks:
      return None

    return self._ConfigData().disks[disk_uuid]

  @_ConfigSync(shared=1)
  def GetDiskInfo(self, disk_uuid):
    """Returns information about a disk.

    This is a simple wrapper over L{_UnlockedGetDiskInfo}.

    """
    return self._UnlockedGetDiskInfo(disk_uuid)

543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
  def _AllInstanceNodes(self, inst_uuid):
    """Compute the set of all disk-related nodes for an instance.

    This abstracts away some work from '_UnlockedGetInstanceNodes'
    and '_UnlockedGetInstanceSecondaryNodes'.

    @type inst_uuid: string
    @param inst_uuid: The UUID of the instance we want to get nodes for
    @rtype: set of strings
    @return: A set of names for all the nodes of the instance

    """
    instance = self._UnlockedGetInstanceInfo(inst_uuid)
    if instance is None:
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)

559
    instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
    all_nodes = []
    for disk in instance_disks:
      all_nodes.extend(disk.all_nodes)
    return (set(all_nodes), instance)

  def _UnlockedGetInstanceNodes(self, inst_uuid):
    """Get all disk-related nodes for an instance.

    For non-DRBD, this will be empty, for DRBD it will contain both
    the primary and the secondaries.

    @type inst_uuid: string
    @param inst_uuid: The UUID of the instance we want to get nodes for
    @rtype: list of strings
    @return: A list of names for all the nodes of the instance

    """
    (all_nodes, instance) = self._AllInstanceNodes(inst_uuid)
    # ensure that primary node is always the first
    all_nodes.discard(instance.primary_node)
    return (instance.primary_node, ) + tuple(all_nodes)

  @_ConfigSync(shared=1)
  def GetInstanceNodes(self, inst_uuid):
    """Get all disk-related nodes for an instance.

    This is just a wrapper over L{_UnlockedGetInstanceNodes}

    """
    return self._UnlockedGetInstanceNodes(inst_uuid)

  def _UnlockedGetInstanceSecondaryNodes(self, inst_uuid):
    """Get the list of secondary nodes.

    @type inst_uuid: string
    @param inst_uuid: The UUID of the instance we want to get nodes for
    @rtype: list of strings
    @return: A list of names for all the secondary nodes of the instance

    """
    (all_nodes, instance) = self._AllInstanceNodes(inst_uuid)
    all_nodes.discard(instance.primary_node)
    return tuple(all_nodes)

  @_ConfigSync(shared=1)
  def GetInstanceSecondaryNodes(self, inst_uuid):
    """Get the list of secondary nodes.

    This is a simple wrapper over L{_UnlockedGetInstanceSecondaryNodes}.

    """
    return self._UnlockedGetInstanceSecondaryNodes(inst_uuid)

613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
  def _UnlockedGetInstanceLVsByNode(self, inst_uuid, lvmap=None):
    """Provide a mapping of node to LVs a given instance owns.

    @type inst_uuid: string
    @param inst_uuid: The UUID of the instance we want to
        compute the LVsByNode for
    @type lvmap: dict
    @param lvmap: Optional dictionary to receive the
        'node' : ['lv', ...] data.
    @rtype: dict or None
    @return: None if lvmap arg is given, otherwise, a dictionary of
        the form { 'node_uuid' : ['volume1', 'volume2', ...], ... };
        volumeN is of the form "vg_name/lv_name", compatible with
        GetVolumeList()

    """
    def _MapLVsByNode(lvmap, devices, node_uuid):
      """Recursive helper function."""
      if not node_uuid in lvmap:
        lvmap[node_uuid] = []

      for dev in devices:
        if dev.dev_type == constants.DT_PLAIN:
          lvmap[node_uuid].append(dev.logical_id[0] + "/" + dev.logical_id[1])

        elif dev.dev_type in constants.DTS_DRBD:
          if dev.children:
            _MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
            _MapLVsByNode(lvmap, dev.children, dev.logical_id[1])

        elif dev.children:
          _MapLVsByNode(lvmap, dev.children, node_uuid)

    instance = self._UnlockedGetInstanceInfo(inst_uuid)
    if instance is None:
      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)

    if lvmap is None:
      lvmap = {}
      ret = lvmap
    else:
      ret = None

656
657
658
    _MapLVsByNode(lvmap,
                  self._UnlockedGetInstanceDisks(instance.uuid),
                  instance.primary_node)
659
660
661
662
663
664
665
666
667
668
669
    return ret

  @_ConfigSync(shared=1)
  def GetInstanceLVsByNode(self, inst_uuid, lvmap=None):
    """Provide a mapping of node to LVs a given instance owns.

    This is a simple wrapper over L{_UnlockedGetInstanceLVsByNode}

    """
    return self._UnlockedGetInstanceLVsByNode(inst_uuid, lvmap=lvmap)

670
  @_ConfigSync(shared=1)
671
672
673
  def GetGroupDiskParams(self, group):
    """Get the disk params populated with inherit chain.

674
    @type group: L{objects.NodeGroup}
675
676
677
678
679
680
681
682
683
    @param group: The group we want to know the params for
    @return: A dict with the filled in disk params

    """
    return self._UnlockedGetGroupDiskParams(group)

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

684
    @type group: L{objects.NodeGroup}
685
686
687
688
    @param group: The group we want to know the params for
    @return: A dict with the filled in disk params

    """
689
690
691
    data = self._ConfigData().cluster.SimpleFillDP(group.diskparams)
    assert isinstance(data, dict), "Not a dictionary: " + str(data)
    return data
692

693
  def _UnlockedGetNetworkMACPrefix(self, net_uuid):
694
695
696
697
    """Return the network mac prefix if it exists or the cluster level default.

    """
    prefix = None
698
    if net_uuid:
699
700
701
      nobj = self._UnlockedGetNetwork(net_uuid)
      if nobj.mac_prefix:
        prefix = nobj.mac_prefix
702
703
704
705
706
707
708
709
710
711

    return prefix

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

    """
    if not prefix:
712
      prefix = self._ConfigData().cluster.mac_prefix
713
714
715
716
717
718
719
720
721
722

    def GenMac():
      byte1 = random.randrange(0, 256)
      byte2 = random.randrange(0, 256)
      byte3 = random.randrange(0, 256)
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
      return mac

    return GenMac

723
  @_ConfigSync(shared=1)
724
  def GenerateMAC(self, net_uuid, ec_id):
Iustin Pop's avatar
Iustin Pop committed
725
726
727
728
729
    """Generate a MAC for an instance.

    This should check the current instances for duplicates.

    """
730
    existing = self._AllMACs()
731
    prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
732
    gen_mac = self._GenerateOneMAC(prefix)
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
733
    return self._temporary_ids.Generate(existing, gen_mac, ec_id)
Iustin Pop's avatar
Iustin Pop committed
734

735
  @_ConfigSync(shared=1)
736
737
  def ReserveMAC(self, mac, ec_id):
    """Reserve a MAC for an instance.
738
739
740
741
742
743

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

    """
    all_macs = self._AllMACs()
744
745
746
    if mac in all_macs:
      raise errors.ReservationError("mac already in use")
    else:
747
      self._temporary_macs.Reserve(ec_id, mac)
748

749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
  def _UnlockedCommitTemporaryIps(self, ec_id):
    """Commit all reserved IP address to their respective pools

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

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

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

    """
    nobj = self._UnlockedGetNetwork(net_uuid)
    pool = network.AddressPool(nobj)
764
    if action == constants.RESERVE_ACTION:
765
      pool.Reserve(address)
766
    elif action == constants.RELEASE_ACTION:
767
768
769
770
771
772
773
774
775
      pool.Release(address)

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

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

    """
776
777
    self._temporary_ips.Reserve(ec_id,
                                (constants.RELEASE_ACTION, address, net_uuid))
778

779
  @_ConfigSync(shared=1)
780
  def ReleaseIp(self, net_uuid, address, ec_id):
781
782
783
784
785
    """Give a specified IP address back to an IP pool.

    This is just a wrapper around _UnlockedReleaseIp.

    """
786
787
    if net_uuid:
      self._UnlockedReleaseIp(net_uuid, address, ec_id)
788

789
  @_ConfigSync(shared=1)
790
  def GenerateIp(self, net_uuid, ec_id):
791
792
793
794
795
796
797
798
    """Find a free IPv4 address for an instance.

    """
    nobj = self._UnlockedGetNetwork(net_uuid)
    pool = network.AddressPool(nobj)

    def gen_one():
      try:
799
800
        ip = pool.GenerateFree()
      except errors.AddressPoolError:
801
        raise errors.ReservationError("Cannot generate IP. Network is full")
802
      return (constants.RESERVE_ACTION, ip, net_uuid)
803

804
    _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
805
806
    return address

807
  def _UnlockedReserveIp(self, net_uuid, address, ec_id, check=True):
808
809
810
811
812
813
814
    """Reserve a given IPv4 address for use by an instance.

    """
    nobj = self._UnlockedGetNetwork(net_uuid)
    pool = network.AddressPool(nobj)
    try:
      isreserved = pool.IsReserved(address)
815
      isextreserved = pool.IsReserved(address, external=True)
816
817
818
819
    except errors.AddressPoolError:
      raise errors.ReservationError("IP address not in network")
    if isreserved:
      raise errors.ReservationError("IP address already in use")
820
821
    if check and isextreserved:
      raise errors.ReservationError("IP is externally reserved")
822

823
824
825
    return self._temporary_ips.Reserve(ec_id,
                                       (constants.RESERVE_ACTION,
                                        address, net_uuid))
826

827
  @_ConfigSync(shared=1)
828
  def ReserveIp(self, net_uuid, address, ec_id, check=True):
829
830
831
    """Reserve a given IPv4 address for use by an instance.

    """
832
    if net_uuid:
833
      return self._UnlockedReserveIp(net_uuid, address, ec_id, check)
834

835
  @_ConfigSync(shared=1)
836
837
838
839
840
841
842
843
844
845
846
  def ReserveLV(self, lv_name, ec_id):
    """Reserve an VG/LV pair for an instance.

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

    """
    all_lvs = self._AllLVs()
    if lv_name in all_lvs:
      raise errors.ReservationError("LV already in use")
    else:
847
      self._temporary_lvs.Reserve(ec_id, lv_name)
848

849
  @_ConfigSync(shared=1)
850
  def GenerateDRBDSecret(self, ec_id):
851
852
853
854
855
    """Generate a DRBD secret.

    This checks the current disks for duplicates.

    """
856
857
858
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
                                            utils.GenerateSecret,
                                            ec_id)
Michael Hanselmann's avatar
Michael Hanselmann committed
859

860
  def _AllLVs(self):
861
862
863
864
    """Compute the list of all LVs.

    """
    lvnames = set()
865
    for instance in self._ConfigData().instances.values():
866
      node_data = self._UnlockedGetInstanceLVsByNode(instance.uuid)
867
868
869
870
      for lv_list in node_data.values():
        lvnames.update(lv_list)
    return lvnames

871
872
873
874
875
  def _AllNICs(self):
    """Compute the list of all NICs.

    """
    nics = []
876
    for instance in self._ConfigData().instances.values():
877
878
879
      nics.extend(instance.nics)
    return nics

880
881
882
883
884
885
886
887
888
889
890
  def _AllIDs(self, include_temporary):
    """Compute the list of all UUIDs and names we have.

    @type include_temporary: boolean
    @param include_temporary: whether to include the _temporary_ids set
    @rtype: set
    @return: a set of IDs

    """
    existing = set()
    if include_temporary:
891
      existing.update(self._temporary_ids.GetReserved())
892
    existing.update(self._AllLVs())
893
894
    existing.update(self._ConfigData().instances.keys())
    existing.update(self._ConfigData().nodes.keys())
895
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
896
897
    return existing

898
  def _GenerateUniqueID(self, ec_id):
899
    """Generate an unique UUID.
900
901
902
903

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

Iustin Pop's avatar
Iustin Pop committed
904
905
    @rtype: string
    @return: the unique id
906
907

    """
908
909
    existing = self._AllIDs(include_temporary=False)
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
910

911
  @_ConfigSync(shared=1)
912
  def GenerateUniqueID(self, ec_id):
913
914
915
916
    """Generate an unique ID.

    This is just a wrapper over the unlocked version.

917
918
    @type ec_id: string
    @param ec_id: unique id for the job to reserve the id to
919
920

    """
921
    return self._GenerateUniqueID(ec_id)
922

Iustin Pop's avatar
Iustin Pop committed
923
924
925
  def _AllMACs(self):
    """Return all MACs present in the config.

Iustin Pop's avatar
Iustin Pop committed
926
927
928
    @rtype: list
    @return: the list of all MACs

Iustin Pop's avatar
Iustin Pop committed
929
930
    """
    result = []
931
    for instance in self._ConfigData().instances.values():
Iustin Pop's avatar
Iustin Pop committed
932
933
934
935
936
      for nic in instance.nics:
        result.append(nic.mac)

    return result

937
938
939
  def _AllDRBDSecrets(self):
    """Return all DRBD secrets present in the config.

Iustin Pop's avatar
Iustin Pop committed
940
941
942
    @rtype: list
    @return: the list of all DRBD secrets

943
944
945
946
947
948
949
950
951
952
    """
    def helper(disk, result):
      """Recursively gather secrets from this disk."""
      if disk.dev_type == constants.DT_DRBD8:
        result.append(disk.logical_id[5])
      if disk.children:
        for child in disk.children:
          helper(child, result)

    result = []
953
954
    for disk in self._ConfigData().disks.values():
      helper(disk, result)
955
956
957

    return result

958
959
960
  @staticmethod
  def _VerifyDisks(data, result):
    """Per-disk verification checks
961

962
    Extends L{result} with diagnostic information about the disks.
963

964
965
    @type data: see L{_ConfigData}
    @param data: configuration data
966

967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
    @type result: list of strings
    @param result: list containing diagnostic messages

    """
    instance_disk_uuids = [d for insts in data.instances.values()
                           for d in insts.disks]
    for disk_uuid in data.disks:
      disk = data.disks[disk_uuid]
      result.extend(["disk %s error: %s" % (disk.uuid, msg)
                     for msg in disk.Verify()])
      if disk.uuid != disk_uuid:
        result.append("disk '%s' is indexed by wrong UUID '%s'" %
                      (disk.name, disk_uuid))
      if disk.uuid not in instance_disk_uuids:
        result.append("disk '%s' is not attached to any instance" %
                      disk.uuid)
983

984
  def _UnlockedVerifyConfig(self):
985
986
    """Verify function.

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

Iustin Pop's avatar
Iustin Pop committed
991
    """
992
    # pylint: disable=R0914
Iustin Pop's avatar
Iustin Pop committed
993
994
    result = []
    seen_macs = []
995
    ports = {}
996
    data = self._ConfigData()
997
    cluster = data.cluster
998
999

    # global cluster checks
1000
    if not cluster.enabled_hypervisors:
1001
      result.append("enabled hypervisors list doesn't have any entries")
1002
    invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
1003
1004
    if invalid_hvs:
      result.append("enabled hypervisors contains invalid entries: %s" %
1005
                    utils.CommaJoin(invalid_hvs))
1006
1007
    missing_hvp = (set(cluster.enabled_hypervisors) -
                   set(cluster.hvparams.keys()))
1008
1009
1010
    if missing_hvp:
      result.append("hypervisor parameters missing for the enabled"
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
1011

1012
1013
1014
1015
1016
1017
1018
1019
    if not cluster.enabled_disk_templates:
      result.append("enabled disk templates list doesn't have any entries")
    invalid_disk_templates = set(cluster.enabled_disk_templates) \
                               - constants.DISK_TEMPLATES
    if invalid_disk_templates:
      result.append("enabled disk templates list contains invalid entries:"
                    " %s" % utils.CommaJoin(invalid_disk_templates))

1020
    if cluster.master_node not in data.nodes:
1021
      result.append("cluster has invalid primary node '%s'" %
1022
                    cluster.master_node)
1023

1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
    def _helper(owner, attr, value, template):
      try:
        utils.ForceDictType(value, template)
      except errors.GenericError, err:
        result.append("%s has invalid %s: %s" % (owner, attr, err))

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

1036
    def _helper_ipolicy(owner, ipolicy, iscluster):
1037
      try:
1038
        objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
1039
1040
      except errors.ConfigurationError, err:
        result.append("%s has invalid instance policy: %s" % (owner, err))
1041
1042
      for key, value in ipolicy.items():
        if key == constants.ISPECS_MINMAX:
1043
1044
          for k in range(len(value)):
            _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
1045
1046
1047
        elif key == constants.ISPECS_STD:
          _helper(owner, "ipolicy/" + key, value,
                  constants.ISPECS_PARAMETER_TYPES)
1048
1049
        else:
          # FIXME: assuming list type
1050
1051
          if key in constants.IPOLICY_PARAMETERS:
            exp_type = float
1052
1053
            # if the value is int, it can be converted into float
            convertible_types = [int]
1054
1055
          else:
            exp_type = list
1056
1057
1058
1059
1060
1061
1062
1063
            convertible_types = []
          # Try to convert from allowed types, if necessary.
          if any(isinstance(value, ct) for ct in convertible_types):
            try:
              value = exp_type(value)
              ipolicy[key] = value
            except ValueError:
              pass
1064
          if not isinstance(value, exp_type):
1065
            result.append("%s has invalid instance policy: for %s,"
1066
1067
                          " expecting %s, got %s" %
                          (owner, key, exp_type.__name__, type(value)))
1068

1069
1070
1071
1072
1073
    def _helper_ispecs(owner, parentkey, params):
      for (key, value) in params.items():
        fullkey = "/".join([parentkey, key])
        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)

1074
1075
1076
1077
1078
1079
1080
1081
    # check cluster parameters
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
            constants.BES_PARAMETER_TYPES)
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
            constants.NICS_PARAMETER_TYPES)
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
            constants.NDS_PARAMETER_TYPES)
1082
    _helper_ipolicy("cluster", cluster.ipolicy, True)
1083

1084
1085
1086
1087
1088
1089
    for disk_template in cluster.diskparams:
      if disk_template not in constants.DTS_HAVE_ACCESS:
        continue

      access = cluster.diskparams[disk_template].get(constants.LDP_ACCESS,
                                                     constants.DISK_KERNELSPACE)
1090
1091
1092
      if access not in constants.DISK_VALID_ACCESS_MODES:
        result.append(
          "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
1093
            disk_template, constants.LDP_ACCESS, access,
1094
1095
1096
1097
            utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
          )
        )

1098
1099
    self._VerifyDisks(data, result)

1100
    # per-instance checks
1101
1102
1103
1104
1105
    for instance_uuid in data.instances:
      instance = data.instances[instance_uuid]
      if instance.uuid != instance_uuid:
        result.append("instance '%s' is indexed by wrong UUID '%s'" %
                      (instance.name, instance_uuid))
Iustin Pop's avatar
Iustin Pop committed
1106
      if instance.primary_node not in data.nodes:
1107
        result.append("instance '%s' has invalid primary node '%s'" %
1108
                      (instance.name, instance.primary_node))
1109
      for snode in self._UnlockedGetInstanceSecondaryNodes(instance.uuid):
Iustin Pop's avatar
Iustin Pop committed
1110
        if snode not in data.nodes:
1111
          result.append("instance '%s' has invalid secondary node '%s'" %
1112
                        (instance.name, snode))
Iustin Pop's avatar
Iustin Pop committed
1113
1114
      for idx, nic in enumerate(instance.nics):
        if nic.mac in seen_macs:
1115
          result.append("instance '%s' has NIC %d mac %s duplicate" %
1116
                        (instance.name, idx, nic.mac))
Iustin Pop's avatar
Iustin Pop committed
1117
1118
        else:
          seen_macs.append(nic.mac)
1119
1120
1121
1122
1123
1124
1125
        if nic.nicparams:
          filled = cluster.SimpleFillNIC(nic.nicparams)
          owner = "instance %s nic %d" % (instance.name, idx)
          _helper(owner, "nicparams",
                  filled, constants.NICS_PARAMETER_TYPES)
          _helper_nic(owner, filled)

1126
1127
1128
      # disk template checks
      if not instance.disk_template in data.cluster.enabled_disk_templates:
        result.append("instance '%s' uses the disabled disk template '%s'." %
1129
                      (instance.name, instance.disk_template))
1130

1131
1132
1133
1134
      # parameter checks
      if instance.beparams:
        _helper("instance %s" % instance.name, "beparams",
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
1135

1136
1137
1138
1139
1140
1141
      # check that disks exists
      for disk_uuid in instance.disks:
        if disk_uuid not in data.disks:
          result.append("Instance '%s' has invalid disk '%s'" %
                        (instance.name, disk_uuid))

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
1142
      instance_disks = self._UnlockedGetInstanceDisks(instance.uuid)
1143
      # gather the drbd ports for duplicate checks
1144
      for (idx, dsk) in enumerate(instance_disks):
Helga Velroyen's avatar
Helga Velroyen committed
1145
        if dsk.dev_type in constants.DTS_DRBD:
1146
1147
1148
          tcp_port = dsk.logical_id[2]
          if tcp_port not in ports:
            ports[tcp_port] = []
1149
          ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
1150
1151
1152
1153
1154
1155
1156
      # gather network port reservation
      net_port = getattr(instance, "network_port", None)
      if net_port is not None:
        if net_port not in ports:
          ports[net_port] = []
        ports[net_port].append((instance.name, "network port"))

1157
      wrong_names = _CheckInstanceDiskIvNames(instance_disks)
1158
1159
1160
1161
1162
1163
1164
1165
      if wrong_names:
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
                         (idx, exp_name, actual_name))
                        for (idx, exp_name, actual_name) in wrong_names)

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

1166
    # cluster-wide pool of free ports
1167
    for free_port in cluster.tcpudp_port_pool:
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
      if free_port not in ports:
        ports[free_port] = []
      ports[free_port].append(("cluster", "port marked as free"))

    # compute tcp/udp duplicate ports
    keys = ports.keys()
    keys.sort()
    for pnum in keys:
      pdata = ports[pnum]
      if len(pdata) > 1:
1178
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
1179
1180
1181
1182
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))

    # highest used tcp port check
    if keys:
1183
      if keys[-1] > cluster.highest_used_port:
1184
        result.append("Highest used port mismatch, saved %s, computed %s" %
1185
                      (cluster.highest_used_port, keys[-1]))
1186

1187
    if not data.nodes[cluster.master_node].master_candidate:
1188
1189
      result.append("Master node is not a master candidate")

1190
    # master candidate checks
1191
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
1192
1193
1194
    if mc_now < mc_max:
      result.append("Not enough master candidates: actual %d, target %d" %
                    (mc_now, mc_max))
1195

1196
    # node checks
Thomas Thrainer's avatar
Thomas Thrainer committed
1197
1198
1199
1200
    for node_uuid, node in data.nodes.items():
      if node.uuid != node_uuid:
        result.append("Node '%s' is indexed by wrong UUID '%s'" %
                      (node.name, node_uuid))
1201
1202
1203
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
        result.append("Node %s state is invalid: master_candidate=%s,"
                      " drain=%s, offline=%s" %
1204
                      (node.name, node.master_candidate, node.drained,
1205
                       node.offline))
1206
1207
1208
1209
1210
1211
1212
      if node.group not in data.nodegroups:
        result.append("Node '%s' has invalid group '%s'" %
                      (node.name, node.group))
      else:
        _helper("node %s" % node.name, "ndparams",
                cluster.FillND(node, data.nodegroups[node.group]),
                constants.NDS_PARAMETER_TYPES)
1213
1214
1215
1216
      used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
      if used_globals:
        result.append("Node '%s' has some global parameters set: %s" %
                      (node.name, utils.CommaJoin(used_globals)))
1217

1218
    # nodegroups checks
1219
    nodegroups_names = set()
1220
1221
1222
    for nodegroup_uuid in data.nodegroups:
      nodegroup = data.nodegroups[nodegroup_uuid]
      if nodegroup.uuid != nodegroup_uuid:
1223
        result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
1224
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
1225
      if utils.UUID_RE.match(nodegroup.name.lower()):
1226
        result.append("node group '%s' (uuid: '%s') has uuid-like name" %
1227
                      (nodegroup.name, nodegroup.uuid))
1228
      if nodegroup.name in nodegroups_names:
1229
        result.append("duplicate node group name '%s'" % nodegroup.name)
1230
1231
      else:
        nodegroups_names.add(nodegroup.name)
1232
      group_name = "group %s" % nodegroup.name
1233
1234
      _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
                      False)
1235
      if nodegroup.ndparams:
1236
        _helper(group_name, "ndparams",
1237
1238
1239
                cluster.SimpleFillND(nodegroup.ndparams),
                constants.NDS_PARAMETER_TYPES)

1240
    # drbd minors check
1241
    logging.debug("The check for DRBD map needs to be implemented in WConfd")
1242

1243
    # IP checks
1244
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
1245
1246
1247
1248
1249
    ips = {}

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

1250
    _AddIpAddress(cluster.master_ip, "cluster_ip")
1251
1252

    for node in data.nodes.values():
1253
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
1254
      if node.secondary_ip != node.primary_ip:
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)

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

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

        if nic_mode == constants.NIC_MODE_BRIDGED:
          link = "bridge:%s" % nic_link
        elif nic_mode == constants.NIC_MODE_ROUTED:
          link = "route:%s" % nic_link
        else:
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)

1273
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
1274
                      "instance:%s/nic:%d" % (instance.name, idx))
1275
1276
1277
1278

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

Iustin Pop's avatar
Iustin Pop committed
1281
1282
    return result

1283
  @_ConfigSync(shared=1)
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
  def VerifyConfig(self):
    """Verify function.

    This is just a wrapper over L{_UnlockedVerifyConfig}.

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

    """
    return self._UnlockedVerifyConfig()

1296
  @_ConfigSync()
1297
1298
1299
  def AddTcpUdpPort(self, port):
    """Adds a new port to the available port pool.

1300
1301
1302
1303
    @warning: this method does not "flush" the configuration (via
        L{_WriteConfig}); callers should do that themselves once the
        configuration is stable

1304
    """
1305
    if not isinstance(port, int):
1306
      raise errors.ProgrammerError("Invalid type passed for port")
1307

1308
    self.