Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 7db4b6d8

History | View | Annotate | Download (87.4 kB)

1
#
2
#
3

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

    
21

    
22
"""Configuration management for Ganeti
23

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

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

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

32
"""
33

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

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

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

    
57

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

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

    
63

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

67
  This only verifies the version of the configuration.
68

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

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

    
76

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

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

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

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

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

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

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

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

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

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

126
    """
127
    assert callable(generate_one_fn)
128

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

    
142

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

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

    
149

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

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

159
  """
160
  result = []
161

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

    
167
  return result
168

    
169

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

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

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

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

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

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

216
    """
217
    self._context = context
218

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

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

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

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

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

    
239
  @locking.ssynchronized(_config_lock, shared=1)
240
  def GetInstanceDiskParams(self, instance):
241
    """Get the disk params populated with inherit chain.
242

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

247
    """
248
    node = self._UnlockedGetNodeInfo(instance.primary_node)
249
    nodegroup = self._UnlockedGetNodeGroup(node.group)
250
    return self._UnlockedGetGroupDiskParams(nodegroup)
251

    
252
  @locking.ssynchronized(_config_lock, shared=1)
253
  def GetGroupDiskParams(self, group):
254
    """Get the disk params populated with inherit chain.
255

256
    @type group: L{objects.NodeGroup}
257
    @param group: The group we want to know the params for
258
    @return: A dict with the filled in disk params
259

260
    """
261
    return self._UnlockedGetGroupDiskParams(group)
262

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

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

270
    """
271
    return self._config_data.cluster.SimpleFillDP(group.diskparams)
272

    
273
  def _UnlockedGetNetworkMACPrefix(self, net_uuid):
274
    """Return the network mac prefix if it exists or the cluster level default.
275

276
    """
277
    prefix = None
278
    if net_uuid:
279
      nobj = self._UnlockedGetNetwork(net_uuid)
280
      if nobj.mac_prefix:
281
        prefix = nobj.mac_prefix
282

    
283
    return prefix
284

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

290
    """
291
    if not prefix:
292
      prefix = self._config_data.cluster.mac_prefix
293

    
294
    def GenMac():
295
      byte1 = random.randrange(0, 256)
296
      byte2 = random.randrange(0, 256)
297
      byte3 = random.randrange(0, 256)
298
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
299
      return mac
300

    
301
    return GenMac
302

    
303
  @locking.ssynchronized(_config_lock, shared=1)
304
  def GenerateMAC(self, net_uuid, ec_id):
305
    """Generate a MAC for an instance.
306

307
    This should check the current instances for duplicates.
308

309
    """
310
    existing = self._AllMACs()
311
    prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
312
    gen_mac = self._GenerateOneMAC(prefix)
313
    return self._temporary_ids.Generate(existing, gen_mac, ec_id)
314

    
315
  @locking.ssynchronized(_config_lock, shared=1)
316
  def ReserveMAC(self, mac, ec_id):
317
    """Reserve a MAC for an instance.
318

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

322
    """
323
    all_macs = self._AllMACs()
324
    if mac in all_macs:
325
      raise errors.ReservationError("mac already in use")
326
    else:
327
      self._temporary_macs.Reserve(ec_id, mac)
328

    
329
  def _UnlockedCommitTemporaryIps(self, ec_id):
330
    """Commit all reserved IP address to their respective pools
331

332
    """
333
    for action, address, net_uuid, external in \
334
          self._temporary_ips.GetECReserved(ec_id):
335
      self._UnlockedCommitIp(action, net_uuid, address, external)
336

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

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

342
    """
343
    nobj = self._UnlockedGetNetwork(net_uuid)
344
    pool = network.Network(nobj)
345
    if action == constants.RESERVE_ACTION:
346
      pool.Reserve(address, external)
347
    elif action == constants.RELEASE_ACTION:
348
      pool.Release(address, external)
349

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

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

356
    """
357
    self._temporary_ips.Reserve(ec_id,
358
                                (constants.RELEASE_ACTION,
359
                                address, net_uuid, external))
360

    
361
  @locking.ssynchronized(_config_lock, shared=1)
362
  def ReleaseIp(self, net_uuid, address, external, ec_id):
363
    """Give a specified IP address back to an IP pool.
364

365
    This is just a wrapper around _UnlockedReleaseIp.
366

367
    """
368
    if net_uuid:
369
      self._UnlockedReleaseIp(net_uuid, address, external, ec_id)
370

    
371
  @locking.ssynchronized(_config_lock, shared=1)
372
  def GenerateIp(self, net_uuid, ec_id):
373
    """Find a free IPv4 address for an instance.
374

375
    """
376
    nobj = self._UnlockedGetNetwork(net_uuid)
377
    pool = network.Network(nobj)
378

    
379
    def gen_one():
380
      try:
381
        ip = pool.GenerateFree()
382
      except errors.NetworkError:
383
        raise errors.OpPrereqError("Cannot generate IP."
384
                                   " Network '%s' is full." % nobj.name,
385
                                   errors.ECODE_STATE)
386
      return (constants.RESERVE_ACTION, ip, net_uuid, False)
387

    
388
    _, address, _, _ = self._temporary_ips.Generate([], gen_one, ec_id)
389
    return address
390

    
391
  def _UnlockedReserveIp(self, net_uuid, address, external, ec_id):
392
    """Reserve a given IPv4 address for use by an instance.
393

394
    """
395
    nobj = self._UnlockedGetNetwork(net_uuid)
396
    pool = network.Network(nobj)
397
    if (address, net_uuid) in self._AllIPs():
398
      raise errors.ConfigurationError("Address '%s' already exists in"
399
                                      " network '%s'" % (address, nobj.name))
400
    if pool.IsReserved(address):
401
      raise errors.OpPrereqError("IP address '%s' already in use." %
402
                                 address, errors.ECODE_EXISTS)
403

    
404
    return self._temporary_ips.Reserve(ec_id,
405
                                       (constants.RESERVE_ACTION,
406
                                        address, net_uuid, external))
407

    
408
  @locking.ssynchronized(_config_lock, shared=1)
409
  def ReserveIp(self, net_uuid, address, external, ec_id):
410
    """Reserve a given IPv4 address for use by an instance.
411

412
    """
413
    if net_uuid:
414
      return self._UnlockedReserveIp(net_uuid, address, external, ec_id)
415

    
416
  @locking.ssynchronized(_config_lock, shared=1)
417
  def ReserveLV(self, lv_name, ec_id):
418
    """Reserve an VG/LV pair for an instance.
419

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

423
    """
424
    all_lvs = self._AllLVs()
425
    if lv_name in all_lvs:
426
      raise errors.ReservationError("LV already in use")
427
    else:
428
      self._temporary_lvs.Reserve(ec_id, lv_name)
429

    
430
  @locking.ssynchronized(_config_lock, shared=1)
431
  def GenerateDRBDSecret(self, ec_id):
432
    """Generate a DRBD secret.
433

434
    This checks the current disks for duplicates.
435

436
    """
437
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
438
                                            utils.GenerateSecret,
439
                                            ec_id)
440

    
441
  def _AllLVs(self):
442
    """Compute the list of all LVs.
443

444
    """
445
    lvnames = set()
446
    for instance in self._config_data.instances.values():
447
      node_data = instance.MapLVsByNode()
448
      for lv_list in node_data.values():
449
        lvnames.update(lv_list)
450
    return lvnames
451

    
452
  def _AllDisks(self):
453
    """Compute the list of all Disks.
454

455
    """
456
    disks = []
457
    for instance in self._config_data.instances.values():
458
      disks.extend(instance.disks)
459
    return disks
460

    
461
  def _AllNICs(self):
462
    """Compute the list of all NICs.
463

464
    """
465
    nics = []
466
    for instance in self._config_data.instances.values():
467
      nics.extend(instance.nics)
468
    return nics
469

    
470
  def _AllIDs(self, include_temporary):
471
    """Compute the list of all UUIDs and names we have.
472

473
    @type include_temporary: boolean
474
    @param include_temporary: whether to include the _temporary_ids set
475
    @rtype: set
476
    @return: a set of IDs
477

478
    """
479
    existing = set()
480
    if include_temporary:
481
      existing.update(self._temporary_ids.GetReserved())
482
    existing.update(self._AllLVs())
483
    existing.update(self._config_data.instances.keys())
484
    existing.update(self._config_data.nodes.keys())
485
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
486
    return existing
487

    
488
  def _GenerateUniqueID(self, ec_id):
489
    """Generate an unique UUID.
490

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

494
    @rtype: string
495
    @return: the unique id
496

497
    """
498
    existing = self._AllIDs(include_temporary=False)
499
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
500

    
501
  @locking.ssynchronized(_config_lock, shared=1)
502
  def GenerateUniqueID(self, ec_id):
503
    """Generate an unique ID.
504

505
    This is just a wrapper over the unlocked version.
506

507
    @type ec_id: string
508
    @param ec_id: unique id for the job to reserve the id to
509

510
    """
511
    return self._GenerateUniqueID(ec_id)
512

    
513
  def _AllMACs(self):
514
    """Return all MACs present in the config.
515

516
    @rtype: list
517
    @return: the list of all MACs
518

519
    """
520
    result = []
521
    for instance in self._config_data.instances.values():
522
      for nic in instance.nics:
523
        result.append(nic.mac)
524

    
525
    return result
526

    
527
  def _AllIPs(self):
528
    """Return all (IP, Network) present in the config.
529

530
    @rtype: list of tuples
531
    @return: the list of all IP, Network tuples
532

533
    """
534
    result = []
535
    for instance in self._config_data.instances.values():
536
      for nic in instance.nics:
537
        result.append((nic.ip, nic.network))
538

    
539
    return result
540

    
541
  def _AllDRBDSecrets(self):
542
    """Return all DRBD secrets present in the config.
543

544
    @rtype: list
545
    @return: the list of all DRBD secrets
546

547
    """
548
    def helper(disk, result):
549
      """Recursively gather secrets from this disk."""
550
      if disk.dev_type == constants.DT_DRBD8:
551
        result.append(disk.logical_id[5])
552
      if disk.children:
553
        for child in disk.children:
554
          helper(child, result)
555

    
556
    result = []
557
    for instance in self._config_data.instances.values():
558
      for disk in instance.disks:
559
        helper(disk, result)
560

    
561
    return result
562

    
563
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
564
    """Compute duplicate disk IDs
565

566
    @type disk: L{objects.Disk}
567
    @param disk: the disk at which to start searching
568
    @type l_ids: list
569
    @param l_ids: list of current logical ids
570
    @type p_ids: list
571
    @param p_ids: list of current physical ids
572
    @rtype: list
573
    @return: a list of error messages
574

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

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

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

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

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

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

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

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

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

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

    
647
    def _helper_ipolicy(owner, ipolicy, iscluster):
648
      try:
649
        objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
650
      except errors.ConfigurationError, err:
651
        result.append("%s has invalid instance policy: %s" % (owner, err))
652
      for key, value in ipolicy.items():
653
        if key == constants.ISPECS_MINMAX:
654
          for k in range(len(value)):
655
            _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
656
        elif key == constants.ISPECS_STD:
657
          _helper(owner, "ipolicy/" + key, value,
658
                  constants.ISPECS_PARAMETER_TYPES)
659
        else:
660
          # FIXME: assuming list type
661
          if key in constants.IPOLICY_PARAMETERS:
662
            exp_type = float
663
          else:
664
            exp_type = list
665
          if not isinstance(value, exp_type):
666
            result.append("%s has invalid instance policy: for %s,"
667
                          " expecting %s, got %s" %
668
                          (owner, key, exp_type.__name__, type(value)))
669

    
670
    def _helper_ispecs(owner, parentkey, params):
671
      for (key, value) in params.items():
672
        fullkey = "/".join([parentkey, key])
673
        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
674

    
675
    # check cluster parameters
676
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
677
            constants.BES_PARAMETER_TYPES)
678
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
679
            constants.NICS_PARAMETER_TYPES)
680
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
681
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
682
            constants.NDS_PARAMETER_TYPES)
683
    _helper_ipolicy("cluster", cluster.ipolicy, True)
684

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

    
711
      # disk template checks
712
      if not instance.disk_template in data.cluster.enabled_disk_templates:
713
        result.append("instance '%s' uses the disabled disk template '%s'." %
714
                      (instance_name, instance.disk_template))
715

    
716
      # parameter checks
717
      if instance.beparams:
718
        _helper("instance %s" % instance.name, "beparams",
719
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
720

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

    
735
      # instance disk verify
736
      for idx, disk in enumerate(instance.disks):
737
        result.extend(["instance '%s' disk %d error: %s" %
738
                       (instance.name, idx, msg) for msg in disk.Verify()])
739
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
740

    
741
      wrong_names = _CheckInstanceDiskIvNames(instance.disks)
742
      if wrong_names:
743
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
744
                         (idx, exp_name, actual_name))
745
                        for (idx, exp_name, actual_name) in wrong_names)
746

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

    
750
    # cluster-wide pool of free ports
751
    for free_port in cluster.tcpudp_port_pool:
752
      if free_port not in ports:
753
        ports[free_port] = []
754
      ports[free_port].append(("cluster", "port marked as free"))
755

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

    
765
    # highest used tcp port check
766
    if keys:
767
      if keys[-1] > cluster.highest_used_port:
768
        result.append("Highest used port mismatch, saved %s, computed %s" %
769
                      (cluster.highest_used_port, keys[-1]))
770

    
771
    if not data.nodes[cluster.master_node].master_candidate:
772
      result.append("Master node is not a master candidate")
773

    
774
    # master candidate checks
775
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
776
    if mc_now < mc_max:
777
      result.append("Not enough master candidates: actual %d, target %d" %
778
                    (mc_now, mc_max))
779

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

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

    
824
    # drbd minors check
825
    _, duplicates = self._UnlockedComputeDRBDMap()
826
    for node, minor, instance_a, instance_b in duplicates:
827
      result.append("DRBD minor %d on node %s is assigned twice to instances"
828
                    " %s and %s" % (minor, node, instance_a, instance_b))
829

    
830
    # IP checks
831
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
832
    ips = {}
833

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

    
837
    _AddIpAddress(cluster.master_ip, "cluster_ip")
838

    
839
    for node in data.nodes.values():
840
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
841
      if node.secondary_ip != node.primary_ip:
842
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
843

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

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

    
853
        if nic_mode == constants.NIC_MODE_BRIDGED:
854
          link = "bridge:%s" % nic_link
855
        elif nic_mode == constants.NIC_MODE_ROUTED:
856
          link = "route:%s" % nic_link
857
        else:
858
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
859

    
860
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
861
                      "instance:%s/nic:%d" % (instance.name, idx))
862

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

    
868
    return result
869

    
870
  @locking.ssynchronized(_config_lock, shared=1)
871
  def VerifyConfig(self):
872
    """Verify function.
873

874
    This is just a wrapper over L{_UnlockedVerifyConfig}.
875

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

880
    """
881
    return self._UnlockedVerifyConfig()
882

    
883
  def _UnlockedSetDiskID(self, disk, node_name):
884
    """Convert the unique ID to the ID needed on the target nodes.
885

886
    This is used only for drbd, which needs ip/port configuration.
887

888
    The routine descends down and updates its children also, because
889
    this helps when the only the top device is passed to the remote
890
    node.
891

892
    This function is for internal use, when the config lock is already held.
893

894
    """
895
    if disk.children:
896
      for child in disk.children:
897
        self._UnlockedSetDiskID(child, node_name)
898

    
899
    if disk.logical_id is None and disk.physical_id is not None:
900
      return
901
    if disk.dev_type == constants.LD_DRBD8:
902
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
903
      if node_name not in (pnode, snode):
904
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
905
                                        node_name)
906
      pnode_info = self._UnlockedGetNodeInfo(pnode)
907
      snode_info = self._UnlockedGetNodeInfo(snode)
908
      if pnode_info is None or snode_info is None:
909
        raise errors.ConfigurationError("Can't find primary or secondary node"
910
                                        " for %s" % str(disk))
911
      p_data = (pnode_info.secondary_ip, port)
912
      s_data = (snode_info.secondary_ip, port)
913
      if pnode == node_name:
914
        disk.physical_id = p_data + s_data + (pminor, secret)
915
      else: # it must be secondary, we tested above
916
        disk.physical_id = s_data + p_data + (sminor, secret)
917
    else:
918
      disk.physical_id = disk.logical_id
919
    return
920

    
921
  @locking.ssynchronized(_config_lock)
922
  def SetDiskID(self, disk, node_name):
923
    """Convert the unique ID to the ID needed on the target nodes.
924

925
    This is used only for drbd, which needs ip/port configuration.
926

927
    The routine descends down and updates its children also, because
928
    this helps when the only the top device is passed to the remote
929
    node.
930

931
    """
932
    return self._UnlockedSetDiskID(disk, node_name)
933

    
934
  @locking.ssynchronized(_config_lock)
935
  def AddTcpUdpPort(self, port):
936
    """Adds a new port to the available port pool.
937

938
    @warning: this method does not "flush" the configuration (via
939
        L{_WriteConfig}); callers should do that themselves once the
940
        configuration is stable
941

942
    """
943
    if not isinstance(port, int):
944
      raise errors.ProgrammerError("Invalid type passed for port")
945

    
946
    self._config_data.cluster.tcpudp_port_pool.add(port)
947

    
948
  @locking.ssynchronized(_config_lock, shared=1)
949
  def GetPortList(self):
950
    """Returns a copy of the current port list.
951

952
    """
953
    return self._config_data.cluster.tcpudp_port_pool.copy()
954

    
955
  @locking.ssynchronized(_config_lock)
956
  def AllocatePort(self):
957
    """Allocate a port.
958

959
    The port will be taken from the available port pool or from the
960
    default port range (and in this case we increase
961
    highest_used_port).
962

963
    """
964
    # If there are TCP/IP ports configured, we use them first.
965
    if self._config_data.cluster.tcpudp_port_pool:
966
      port = self._config_data.cluster.tcpudp_port_pool.pop()
967
    else:
968
      port = self._config_data.cluster.highest_used_port + 1
969
      if port >= constants.LAST_DRBD_PORT:
970
        raise errors.ConfigurationError("The highest used port is greater"
971
                                        " than %s. Aborting." %
972
                                        constants.LAST_DRBD_PORT)
973
      self._config_data.cluster.highest_used_port = port
974

    
975
    self._WriteConfig()
976
    return port
977

    
978
  def _UnlockedComputeDRBDMap(self):
979
    """Compute the used DRBD minor/nodes.
980

981
    @rtype: (dict, list)
982
    @return: dictionary of node_name: dict of minor: instance_name;
983
        the returned dict will have all the nodes in it (even if with
984
        an empty list), and a list of duplicates; if the duplicates
985
        list is not empty, the configuration is corrupted and its caller
986
        should raise an exception
987

988
    """
989
    def _AppendUsedPorts(instance_name, disk, used):
990
      duplicates = []
991
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
992
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
993
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
994
          assert node in used, ("Node '%s' of instance '%s' not found"
995
                                " in node list" % (node, instance_name))
996
          if port in used[node]:
997
            duplicates.append((node, port, instance_name, used[node][port]))
998
          else:
999
            used[node][port] = instance_name
1000
      if disk.children:
1001
        for child in disk.children:
1002
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
1003
      return duplicates
1004

    
1005
    duplicates = []
1006
    my_dict = dict((node, {}) for node in self._config_data.nodes)
1007
    for instance in self._config_data.instances.itervalues():
1008
      for disk in instance.disks:
1009
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
1010
    for (node, minor), instance in self._temporary_drbds.iteritems():
1011
      if minor in my_dict[node] and my_dict[node][minor] != instance:
1012
        duplicates.append((node, minor, instance, my_dict[node][minor]))
1013
      else:
1014
        my_dict[node][minor] = instance
1015
    return my_dict, duplicates
1016

    
1017
  @locking.ssynchronized(_config_lock)
1018
  def ComputeDRBDMap(self):
1019
    """Compute the used DRBD minor/nodes.
1020

1021
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
1022

1023
    @return: dictionary of node_name: dict of minor: instance_name;
1024
        the returned dict will have all the nodes in it (even if with
1025
        an empty list).
1026

1027
    """
1028
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1029
    if duplicates:
1030
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1031
                                      str(duplicates))
1032
    return d_map
1033

    
1034
  @locking.ssynchronized(_config_lock)
1035
  def AllocateDRBDMinor(self, nodes, instance):
1036
    """Allocate a drbd minor.
1037

1038
    The free minor will be automatically computed from the existing
1039
    devices. A node can be given multiple times in order to allocate
1040
    multiple minors. The result is the list of minors, in the same
1041
    order as the passed nodes.
1042

1043
    @type instance: string
1044
    @param instance: the instance for which we allocate minors
1045

1046
    """
1047
    assert isinstance(instance, basestring), \
1048
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
1049

    
1050
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1051
    if duplicates:
1052
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1053
                                      str(duplicates))
1054
    result = []
1055
    for nname in nodes:
1056
      ndata = d_map[nname]
1057
      if not ndata:
1058
        # no minors used, we can start at 0
1059
        result.append(0)
1060
        ndata[0] = instance
1061
        self._temporary_drbds[(nname, 0)] = instance
1062
        continue
1063
      keys = ndata.keys()
1064
      keys.sort()
1065
      ffree = utils.FirstFree(keys)
1066
      if ffree is None:
1067
        # return the next minor
1068
        # TODO: implement high-limit check
1069
        minor = keys[-1] + 1
1070
      else:
1071
        minor = ffree
1072
      # double-check minor against current instances
1073
      assert minor not in d_map[nname], \
1074
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
1075
              " already allocated to instance %s" %
1076
              (minor, nname, d_map[nname][minor]))
1077
      ndata[minor] = instance
1078
      # double-check minor against reservation
1079
      r_key = (nname, minor)
1080
      assert r_key not in self._temporary_drbds, \
1081
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
1082
              " reserved for instance %s" %
1083
              (minor, nname, self._temporary_drbds[r_key]))
1084
      self._temporary_drbds[r_key] = instance
1085
      result.append(minor)
1086
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1087
                  nodes, result)
1088
    return result
1089

    
1090
  def _UnlockedReleaseDRBDMinors(self, instance):
1091
    """Release temporary drbd minors allocated for a given instance.
1092

1093
    @type instance: string
1094
    @param instance: the instance for which temporary minors should be
1095
                     released
1096

1097
    """
1098
    assert isinstance(instance, basestring), \
1099
           "Invalid argument passed to ReleaseDRBDMinors"
1100
    for key, name in self._temporary_drbds.items():
1101
      if name == instance:
1102
        del self._temporary_drbds[key]
1103

    
1104
  @locking.ssynchronized(_config_lock)
1105
  def ReleaseDRBDMinors(self, instance):
1106
    """Release temporary drbd minors allocated for a given instance.
1107

1108
    This should be called on the error paths, on the success paths
1109
    it's automatically called by the ConfigWriter add and update
1110
    functions.
1111

1112
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1113

1114
    @type instance: string
1115
    @param instance: the instance for which temporary minors should be
1116
                     released
1117

1118
    """
1119
    self._UnlockedReleaseDRBDMinors(instance)
1120

    
1121
  @locking.ssynchronized(_config_lock, shared=1)
1122
  def GetConfigVersion(self):
1123
    """Get the configuration version.
1124

1125
    @return: Config version
1126

1127
    """
1128
    return self._config_data.version
1129

    
1130
  @locking.ssynchronized(_config_lock, shared=1)
1131
  def GetClusterName(self):
1132
    """Get cluster name.
1133

1134
    @return: Cluster name
1135

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

    
1139
  @locking.ssynchronized(_config_lock, shared=1)
1140
  def GetMasterNode(self):
1141
    """Get the hostname of the master node for this cluster.
1142

1143
    @return: Master hostname
1144

1145
    """
1146
    return self._config_data.cluster.master_node
1147

    
1148
  @locking.ssynchronized(_config_lock, shared=1)
1149
  def GetMasterIP(self):
1150
    """Get the IP of the master node for this cluster.
1151

1152
    @return: Master IP
1153

1154
    """
1155
    return self._config_data.cluster.master_ip
1156

    
1157
  @locking.ssynchronized(_config_lock, shared=1)
1158
  def GetMasterNetdev(self):
1159
    """Get the master network device for this cluster.
1160

1161
    """
1162
    return self._config_data.cluster.master_netdev
1163

    
1164
  @locking.ssynchronized(_config_lock, shared=1)
1165
  def GetMasterNetmask(self):
1166
    """Get the netmask of the master node for this cluster.
1167

1168
    """
1169
    return self._config_data.cluster.master_netmask
1170

    
1171
  @locking.ssynchronized(_config_lock, shared=1)
1172
  def GetUseExternalMipScript(self):
1173
    """Get flag representing whether to use the external master IP setup script.
1174

1175
    """
1176
    return self._config_data.cluster.use_external_mip_script
1177

    
1178
  @locking.ssynchronized(_config_lock, shared=1)
1179
  def GetFileStorageDir(self):
1180
    """Get the file storage dir for this cluster.
1181

1182
    """
1183
    return self._config_data.cluster.file_storage_dir
1184

    
1185
  @locking.ssynchronized(_config_lock, shared=1)
1186
  def GetSharedFileStorageDir(self):
1187
    """Get the shared file storage dir for this cluster.
1188

1189
    """
1190
    return self._config_data.cluster.shared_file_storage_dir
1191

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

1196
    """
1197
    return self._config_data.cluster.enabled_hypervisors[0]
1198

    
1199
  @locking.ssynchronized(_config_lock, shared=1)
1200
  def GetHostKey(self):
1201
    """Return the rsa hostkey from the config.
1202

1203
    @rtype: string
1204
    @return: the rsa hostkey
1205

1206
    """
1207
    return self._config_data.cluster.rsahostkeypub
1208

    
1209
  @locking.ssynchronized(_config_lock, shared=1)
1210
  def GetDefaultIAllocator(self):
1211
    """Get the default instance allocator for this cluster.
1212

1213
    """
1214
    return self._config_data.cluster.default_iallocator
1215

    
1216
  @locking.ssynchronized(_config_lock, shared=1)
1217
  def GetPrimaryIPFamily(self):
1218
    """Get cluster primary ip family.
1219

1220
    @return: primary ip family
1221

1222
    """
1223
    return self._config_data.cluster.primary_ip_family
1224

    
1225
  @locking.ssynchronized(_config_lock, shared=1)
1226
  def GetMasterNetworkParameters(self):
1227
    """Get network parameters of the master node.
1228

1229
    @rtype: L{object.MasterNetworkParameters}
1230
    @return: network parameters of the master node
1231

1232
    """
1233
    cluster = self._config_data.cluster
1234
    result = objects.MasterNetworkParameters(
1235
      name=cluster.master_node, ip=cluster.master_ip,
1236
      netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1237
      ip_family=cluster.primary_ip_family)
1238

    
1239
    return result
1240

    
1241
  @locking.ssynchronized(_config_lock)
1242
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1243
    """Add a node group to the configuration.
1244

1245
    This method calls group.UpgradeConfig() to fill any missing attributes
1246
    according to their default values.
1247

1248
    @type group: L{objects.NodeGroup}
1249
    @param group: the NodeGroup object to add
1250
    @type ec_id: string
1251
    @param ec_id: unique id for the job to use when creating a missing UUID
1252
    @type check_uuid: bool
1253
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1254
                       it does, ensure that it does not exist in the
1255
                       configuration already
1256

1257
    """
1258
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1259
    self._WriteConfig()
1260

    
1261
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1262
    """Add a node group to the configuration.
1263

1264
    """
1265
    logging.info("Adding node group %s to configuration", group.name)
1266

    
1267
    # Some code might need to add a node group with a pre-populated UUID
1268
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1269
    # the "does this UUID" exist already check.
1270
    if check_uuid:
1271
      self._EnsureUUID(group, ec_id)
1272

    
1273
    try:
1274
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1275
    except errors.OpPrereqError:
1276
      pass
1277
    else:
1278
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1279
                                 " node group (UUID: %s)" %
1280
                                 (group.name, existing_uuid),
1281
                                 errors.ECODE_EXISTS)
1282

    
1283
    group.serial_no = 1
1284
    group.ctime = group.mtime = time.time()
1285
    group.UpgradeConfig()
1286

    
1287
    self._config_data.nodegroups[group.uuid] = group
1288
    self._config_data.cluster.serial_no += 1
1289

    
1290
  @locking.ssynchronized(_config_lock)
1291
  def RemoveNodeGroup(self, group_uuid):
1292
    """Remove a node group from the configuration.
1293

1294
    @type group_uuid: string
1295
    @param group_uuid: the UUID of the node group to remove
1296

1297
    """
1298
    logging.info("Removing node group %s from configuration", group_uuid)
1299

    
1300
    if group_uuid not in self._config_data.nodegroups:
1301
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1302

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

    
1306
    del self._config_data.nodegroups[group_uuid]
1307
    self._config_data.cluster.serial_no += 1
1308
    self._WriteConfig()
1309

    
1310
  def _UnlockedLookupNodeGroup(self, target):
1311
    """Lookup a node group's UUID.
1312

1313
    @type target: string or None
1314
    @param target: group name or UUID or None to look for the default
1315
    @rtype: string
1316
    @return: nodegroup UUID
1317
    @raises errors.OpPrereqError: when the target group cannot be found
1318

1319
    """
1320
    if target is None:
1321
      if len(self._config_data.nodegroups) != 1:
1322
        raise errors.OpPrereqError("More than one node group exists. Target"
1323
                                   " group must be specified explicitly.")
1324
      else:
1325
        return self._config_data.nodegroups.keys()[0]
1326
    if target in self._config_data.nodegroups:
1327
      return target
1328
    for nodegroup in self._config_data.nodegroups.values():
1329
      if nodegroup.name == target:
1330
        return nodegroup.uuid
1331
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1332
                               errors.ECODE_NOENT)
1333

    
1334
  @locking.ssynchronized(_config_lock, shared=1)
1335
  def LookupNodeGroup(self, target):
1336
    """Lookup a node group's UUID.
1337

1338
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1339

1340
    @type target: string or None
1341
    @param target: group name or UUID or None to look for the default
1342
    @rtype: string
1343
    @return: nodegroup UUID
1344

1345
    """
1346
    return self._UnlockedLookupNodeGroup(target)
1347

    
1348
  def _UnlockedGetNodeGroup(self, uuid):
1349
    """Lookup a node group.
1350

1351
    @type uuid: string
1352
    @param uuid: group UUID
1353
    @rtype: L{objects.NodeGroup} or None
1354
    @return: nodegroup object, or None if not found
1355

1356
    """
1357
    if uuid not in self._config_data.nodegroups:
1358
      return None
1359

    
1360
    return self._config_data.nodegroups[uuid]
1361

    
1362
  @locking.ssynchronized(_config_lock, shared=1)
1363
  def GetNodeGroup(self, uuid):
1364
    """Lookup a node group.
1365

1366
    @type uuid: string
1367
    @param uuid: group UUID
1368
    @rtype: L{objects.NodeGroup} or None
1369
    @return: nodegroup object, or None if not found
1370

1371
    """
1372
    return self._UnlockedGetNodeGroup(uuid)
1373

    
1374
  @locking.ssynchronized(_config_lock, shared=1)
1375
  def GetAllNodeGroupsInfo(self):
1376
    """Get the configuration of all node groups.
1377

1378
    """
1379
    return dict(self._config_data.nodegroups)
1380

    
1381
  @locking.ssynchronized(_config_lock, shared=1)
1382
  def GetNodeGroupList(self):
1383
    """Get a list of node groups.
1384

1385
    """
1386
    return self._config_data.nodegroups.keys()
1387

    
1388
  @locking.ssynchronized(_config_lock, shared=1)
1389
  def GetNodeGroupMembersByNodes(self, nodes):
1390
    """Get nodes which are member in the same nodegroups as the given nodes.
1391

1392
    """
1393
    ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
1394
    return frozenset(member_name
1395
                     for node_name in nodes
1396
                     for member_name in
1397
                       self._UnlockedGetNodeGroup(ngfn(node_name)).members)
1398

    
1399
  @locking.ssynchronized(_config_lock, shared=1)
1400
  def GetMultiNodeGroupInfo(self, group_uuids):
1401
    """Get the configuration of multiple node groups.
1402

1403
    @param group_uuids: List of node group UUIDs
1404
    @rtype: list
1405
    @return: List of tuples of (group_uuid, group_info)
1406

1407
    """
1408
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1409

    
1410
  @locking.ssynchronized(_config_lock)
1411
  def AddInstance(self, instance, ec_id):
1412
    """Add an instance to the config.
1413

1414
    This should be used after creating a new instance.
1415

1416
    @type instance: L{objects.Instance}
1417
    @param instance: the instance object
1418

1419
    """
1420
    if not isinstance(instance, objects.Instance):
1421
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1422

    
1423
    if instance.disk_template != constants.DT_DISKLESS:
1424
      all_lvs = instance.MapLVsByNode()
1425
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1426

    
1427
    all_macs = self._AllMACs()
1428
    for nic in instance.nics:
1429
      if nic.mac in all_macs:
1430
        raise errors.ConfigurationError("Cannot add instance %s:"
1431
                                        " MAC address '%s' already in use." %
1432
                                        (instance.name, nic.mac))
1433

    
1434
    self._EnsureUUID(instance, ec_id)
1435

    
1436
    instance.serial_no = 1
1437
    instance.ctime = instance.mtime = time.time()
1438
    self._config_data.instances[instance.name] = instance
1439
    self._config_data.cluster.serial_no += 1
1440
    self._UnlockedReleaseDRBDMinors(instance.name)
1441
    self._UnlockedCommitTemporaryIps(ec_id)
1442
    self._WriteConfig()
1443

    
1444
  def _EnsureUUID(self, item, ec_id):
1445
    """Ensures a given object has a valid UUID.
1446

1447
    @param item: the instance or node to be checked
1448
    @param ec_id: the execution context id for the uuid reservation
1449

1450
    """
1451
    if not item.uuid:
1452
      item.uuid = self._GenerateUniqueID(ec_id)
1453
    elif item.uuid in self._AllIDs(include_temporary=True):
1454
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1455
                                      " in use" % (item.name, item.uuid))
1456

    
1457
  def _SetInstanceStatus(self, instance_name, status):
1458
    """Set the instance's status to a given value.
1459

1460
    """
1461
    assert status in constants.ADMINST_ALL, \
1462
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1463

    
1464
    if instance_name not in self._config_data.instances:
1465
      raise errors.ConfigurationError("Unknown instance '%s'" %
1466
                                      instance_name)
1467
    instance = self._config_data.instances[instance_name]
1468
    if instance.admin_state != status:
1469
      instance.admin_state = status
1470
      instance.serial_no += 1
1471
      instance.mtime = time.time()
1472
      self._WriteConfig()
1473

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

1478
    """
1479
    self._SetInstanceStatus(instance_name, constants.ADMINST_UP)
1480

    
1481
  @locking.ssynchronized(_config_lock)
1482
  def MarkInstanceOffline(self, instance_name):
1483
    """Mark the instance status to down in the config.
1484

1485
    """
1486
    self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE)
1487

    
1488
  @locking.ssynchronized(_config_lock)
1489
  def RemoveInstance(self, instance_name):
1490
    """Remove the instance from the configuration.
1491

1492
    """
1493
    if instance_name not in self._config_data.instances:
1494
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1495

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

    
1503
    instance = self._UnlockedGetInstanceInfo(instance_name)
1504

    
1505
    for nic in instance.nics:
1506
      if nic.network and nic.ip:
1507
        # Return all IP addresses to the respective address pools
1508
        self._UnlockedCommitIp(constants.RELEASE_ACTION,
1509
                               nic.network, nic.ip, False)
1510

    
1511
    del self._config_data.instances[instance_name]
1512
    self._config_data.cluster.serial_no += 1
1513
    self._WriteConfig()
1514

    
1515
  @locking.ssynchronized(_config_lock)
1516
  def RenameInstance(self, old_name, new_name):
1517
    """Rename an instance.
1518

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

1523
    """
1524
    if old_name not in self._config_data.instances:
1525
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1526

    
1527
    # Operate on a copy to not loose instance object in case of a failure
1528
    inst = self._config_data.instances[old_name].Copy()
1529
    inst.name = new_name
1530

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

    
1540
    # Actually replace instance object
1541
    del self._config_data.instances[old_name]
1542
    self._config_data.instances[inst.name] = inst
1543

    
1544
    # Force update of ssconf files
1545
    self._config_data.cluster.serial_no += 1
1546

    
1547
    self._WriteConfig()
1548

    
1549
  @locking.ssynchronized(_config_lock)
1550
  def MarkInstanceDown(self, instance_name):
1551
    """Mark the status of an instance to down in the configuration.
1552

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

    
1556
  def _UnlockedGetInstanceList(self):
1557
    """Get the list of instances.
1558

1559
    This function is for internal use, when the config lock is already held.
1560

1561
    """
1562
    return self._config_data.instances.keys()
1563

    
1564
  @locking.ssynchronized(_config_lock, shared=1)
1565
  def GetInstanceList(self):
1566
    """Get the list of instances.
1567

1568
    @return: array of instances, ex. ['instance2.example.com',
1569
        'instance1.example.com']
1570

1571
    """
1572
    return self._UnlockedGetInstanceList()
1573

    
1574
  def ExpandInstanceName(self, short_name):
1575
    """Attempt to expand an incomplete instance name.
1576

1577
    """
1578
    # Locking is done in L{ConfigWriter.GetInstanceList}
1579
    return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1580

    
1581
  def _UnlockedGetInstanceInfo(self, instance_name):
1582
    """Returns information about an instance.
1583

1584
    This function is for internal use, when the config lock is already held.
1585

1586
    """
1587
    if instance_name not in self._config_data.instances:
1588
      return None
1589

    
1590
    return self._config_data.instances[instance_name]
1591

    
1592
  @locking.ssynchronized(_config_lock, shared=1)
1593
  def GetInstanceInfo(self, instance_name):
1594
    """Returns information about an instance.
1595

1596
    It takes the information from the configuration file. Other information of
1597
    an instance are taken from the live systems.
1598

1599
    @param instance_name: name of the instance, e.g.
1600
        I{instance1.example.com}
1601

1602
    @rtype: L{objects.Instance}
1603
    @return: the instance object
1604

1605
    """
1606
    return self._UnlockedGetInstanceInfo(instance_name)
1607

    
1608
  @locking.ssynchronized(_config_lock, shared=1)
1609
  def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1610
    """Returns set of node group UUIDs for instance's nodes.
1611

1612
    @rtype: frozenset
1613

1614
    """
1615
    instance = self._UnlockedGetInstanceInfo(instance_name)
1616
    if not instance:
1617
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1618

    
1619
    if primary_only:
1620
      nodes = [instance.primary_node]
1621
    else:
1622
      nodes = instance.all_nodes
1623

    
1624
    return frozenset(self._UnlockedGetNodeInfo(node_name).group
1625
                     for node_name in nodes)
1626

    
1627
  @locking.ssynchronized(_config_lock, shared=1)
1628
  def GetInstanceNetworks(self, instance_name):
1629
    """Returns set of network UUIDs for instance's nics.
1630

1631
    @rtype: frozenset
1632

1633
    """
1634
    instance = self._UnlockedGetInstanceInfo(instance_name)
1635
    if not instance:
1636
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1637

    
1638
    networks = set()
1639
    for nic in instance.nics:
1640
      if nic.network:
1641
        networks.add(nic.network)
1642

    
1643
    return frozenset(networks)
1644

    
1645
  @locking.ssynchronized(_config_lock, shared=1)
1646
  def GetMultiInstanceInfo(self, instances):
1647
    """Get the configuration of multiple instances.
1648

1649
    @param instances: list of instance names
1650
    @rtype: list
1651
    @return: list of tuples (instance, instance_info), where
1652
        instance_info is what would GetInstanceInfo return for the
1653
        node, while keeping the original order
1654

1655
    """
1656
    return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1657

    
1658
  @locking.ssynchronized(_config_lock, shared=1)
1659
  def GetAllInstancesInfo(self):
1660
    """Get the configuration of all instances.
1661

1662
    @rtype: dict
1663
    @return: dict of (instance, instance_info), where instance_info is what
1664
              would GetInstanceInfo return for the node
1665

1666
    """
1667
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1668
                    for instance in self._UnlockedGetInstanceList()])
1669
    return my_dict
1670

    
1671
  @locking.ssynchronized(_config_lock, shared=1)
1672
  def GetInstancesInfoByFilter(self, filter_fn):
1673
    """Get instance configuration with a filter.
1674

1675
    @type filter_fn: callable
1676
    @param filter_fn: Filter function receiving instance object as parameter,
1677
      returning boolean. Important: this function is called while the
1678
      configuration locks is held. It must not do any complex work or call
1679
      functions potentially leading to a deadlock. Ideally it doesn't call any
1680
      other functions and just compares instance attributes.
1681

1682
    """
1683
    return dict((name, inst)
1684
                for (name, inst) in self._config_data.instances.items()
1685
                if filter_fn(inst))
1686

    
1687
  @locking.ssynchronized(_config_lock)
1688
  def AddNode(self, node, ec_id):
1689
    """Add a node to the configuration.
1690

1691
    @type node: L{objects.Node}
1692
    @param node: a Node instance
1693

1694
    """
1695
    logging.info("Adding node %s to configuration", node.name)
1696

    
1697
    self._EnsureUUID(node, ec_id)
1698

    
1699
    node.serial_no = 1
1700
    node.ctime = node.mtime = time.time()
1701
    self._UnlockedAddNodeToGroup(node.name, node.group)
1702
    self._config_data.nodes[node.name] = node
1703
    self._config_data.cluster.serial_no += 1
1704
    self._WriteConfig()
1705

    
1706
  @locking.ssynchronized(_config_lock)
1707
  def RemoveNode(self, node_name):
1708
    """Remove a node from the configuration.
1709

1710
    """
1711
    logging.info("Removing node %s from configuration", node_name)
1712

    
1713
    if node_name not in self._config_data.nodes:
1714
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1715

    
1716
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1717
    del self._config_data.nodes[node_name]
1718
    self._config_data.cluster.serial_no += 1
1719
    self._WriteConfig()
1720

    
1721
  def ExpandNodeName(self, short_name):
1722
    """Attempt to expand an incomplete node name.
1723

1724
    """
1725
    # Locking is done in L{ConfigWriter.GetNodeList}
1726
    return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1727

    
1728
  def _UnlockedGetNodeInfo(self, node_name):
1729
    """Get the configuration of a node, as stored in the config.
1730

1731
    This function is for internal use, when the config lock is already
1732
    held.
1733

1734
    @param node_name: the node name, e.g. I{node1.example.com}
1735

1736
    @rtype: L{objects.Node}
1737
    @return: the node object
1738

1739
    """
1740
    if node_name not in self._config_data.nodes:
1741
      return None
1742

    
1743
    return self._config_data.nodes[node_name]
1744

    
1745
  @locking.ssynchronized(_config_lock, shared=1)
1746
  def GetNodeInfo(self, node_name):
1747
    """Get the configuration of a node, as stored in the config.
1748

1749
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1750

1751
    @param node_name: the node name, e.g. I{node1.example.com}
1752

1753
    @rtype: L{objects.Node}
1754
    @return: the node object
1755

1756
    """
1757
    return self._UnlockedGetNodeInfo(node_name)
1758

    
1759
  @locking.ssynchronized(_config_lock, shared=1)
1760
  def GetNodeInstances(self, node_name):
1761
    """Get the instances of a node, as stored in the config.
1762

1763
    @param node_name: the node name, e.g. I{node1.example.com}
1764

1765
    @rtype: (list, list)
1766
    @return: a tuple with two lists: the primary and the secondary instances
1767

1768
    """
1769
    pri = []
1770
    sec = []
1771
    for inst in self._config_data.instances.values():
1772
      if inst.primary_node == node_name:
1773
        pri.append(inst.name)
1774
      if node_name in inst.secondary_nodes:
1775
        sec.append(inst.name)
1776
    return (pri, sec)
1777

    
1778
  @locking.ssynchronized(_config_lock, shared=1)
1779
  def GetNodeGroupInstances(self, uuid, primary_only=False):
1780
    """Get the instances of a node group.
1781

1782
    @param uuid: Node group UUID
1783
    @param primary_only: Whether to only consider primary nodes
1784
    @rtype: frozenset
1785
    @return: List of instance names in node group
1786

1787
    """
1788
    if primary_only:
1789
      nodes_fn = lambda inst: [inst.primary_node]
1790
    else:
1791
      nodes_fn = lambda inst: inst.all_nodes
1792

    
1793
    return frozenset(inst.name
1794
                     for inst in self._config_data.instances.values()
1795
                     for node_name in nodes_fn(inst)
1796
                     if self._UnlockedGetNodeInfo(node_name).group == uuid)
1797

    
1798
  def _UnlockedGetNodeList(self):
1799
    """Return the list of nodes which are in the configuration.
1800

1801
    This function is for internal use, when the config lock is already
1802
    held.
1803

1804
    @rtype: list
1805

1806
    """
1807
    return self._config_data.nodes.keys()
1808

    
1809
  @locking.ssynchronized(_config_lock, shared=1)
1810
  def GetNodeList(self):
1811
    """Return the list of nodes which are in the configuration.
1812

1813
    """
1814
    return self._UnlockedGetNodeList()
1815

    
1816
  def _UnlockedGetOnlineNodeList(self):
1817
    """Return the list of nodes which are online.
1818

1819
    """
1820
    all_nodes = [self._UnlockedGetNodeInfo(node)
1821
                 for node in self._UnlockedGetNodeList()]
1822
    return [node.name for node in all_nodes if not node.offline]
1823

    
1824
  @locking.ssynchronized(_config_lock, shared=1)
1825
  def GetOnlineNodeList(self):
1826
    """Return the list of nodes which are online.
1827

1828
    """
1829
    return self._UnlockedGetOnlineNodeList()
1830

    
1831
  @locking.ssynchronized(_config_lock, shared=1)
1832
  def GetVmCapableNodeList(self):
1833
    """Return the list of nodes which are not vm capable.
1834

1835
    """
1836
    all_nodes = [self._UnlockedGetNodeInfo(node)
1837
                 for node in self._UnlockedGetNodeList()]
1838
    return [node.name for node in all_nodes if node.vm_capable]
1839

    
1840
  @locking.ssynchronized(_config_lock, shared=1)
1841
  def GetNonVmCapableNodeList(self):
1842
    """Return the list of nodes which are not vm capable.
1843

1844
    """
1845
    all_nodes = [self._UnlockedGetNodeInfo(node)
1846
                 for node in self._UnlockedGetNodeList()]
1847
    return [node.name for node in all_nodes if not node.vm_capable]
1848

    
1849
  @locking.ssynchronized(_config_lock, shared=1)
1850
  def GetMultiNodeInfo(self, nodes):
1851
    """Get the configuration of multiple nodes.
1852

1853
    @param nodes: list of node names
1854
    @rtype: list
1855
    @return: list of tuples of (node, node_info), where node_info is
1856
        what would GetNodeInfo return for the node, in the original
1857
        order
1858

1859
    """
1860
    return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1861

    
1862
  @locking.ssynchronized(_config_lock, shared=1)
1863
  def GetAllNodesInfo(self):
1864
    """Get the configuration of all nodes.
1865

1866
    @rtype: dict
1867
    @return: dict of (node, node_info), where node_info is what
1868
              would GetNodeInfo return for the node
1869

1870
    """
1871
    return self._UnlockedGetAllNodesInfo()
1872

    
1873
  def _UnlockedGetAllNodesInfo(self):
1874
    """Gets configuration of all nodes.
1875

1876
    @note: See L{GetAllNodesInfo}
1877

1878
    """
1879
    return dict([(node, self._UnlockedGetNodeInfo(node))
1880
                 for node in self._UnlockedGetNodeList()])
1881

    
1882
  @locking.ssynchronized(_config_lock, shared=1)
1883
  def GetNodeGroupsFromNodes(self, nodes):
1884
    """Returns groups for a list of nodes.
1885

1886
    @type nodes: list of string
1887
    @param nodes: List of node names
1888
    @rtype: frozenset
1889

1890
    """
1891
    return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1892

    
1893
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1894
    """Get the number of current and maximum desired and possible candidates.
1895

1896
    @type exceptions: list
1897
    @param exceptions: if passed, list of nodes that should be ignored
1898
    @rtype: tuple
1899
    @return: tuple of (current, desired and possible, possible)
1900

1901
    """
1902
    mc_now = mc_should = mc_max = 0
1903
    for node in self._config_data.nodes.values():
1904
      if exceptions and node.name in exceptions:
1905
        continue
1906
      if not (node.offline or node.drained) and node.master_capable:
1907
        mc_max += 1
1908
      if node.master_candidate:
1909
        mc_now += 1
1910
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1911
    return (mc_now, mc_should, mc_max)
1912

    
1913
  @locking.ssynchronized(_config_lock, shared=1)
1914
  def GetMasterCandidateStats(self, exceptions=None):
1915
    """Get the number of current and maximum possible candidates.
1916

1917
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1918

1919
    @type exceptions: list
1920
    @param exceptions: if passed, list of nodes that should be ignored
1921
    @rtype: tuple
1922
    @return: tuple of (current, max)
1923

1924
    """
1925
    return self._UnlockedGetMasterCandidateStats(exceptions)
1926

    
1927
  @locking.ssynchronized(_config_lock)
1928
  def MaintainCandidatePool(self, exceptions):
1929
    """Try to grow the candidate pool to the desired size.
1930

1931
    @type exceptions: list
1932
    @param exceptions: if passed, list of nodes that should be ignored
1933
    @rtype: list
1934
    @return: list with the adjusted nodes (L{objects.Node} instances)
1935

1936
    """
1937
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1938
    mod_list = []
1939
    if mc_now < mc_max:
1940
      node_list = self._config_data.nodes.keys()
1941
      random.shuffle(node_list)
1942
      for name in node_list:
1943
        if mc_now >= mc_max:
1944
          break
1945
        node = self._config_data.nodes[name]
1946
        if (node.master_candidate or node.offline or node.drained or
1947
            node.name in exceptions or not node.master_capable):
1948
          continue
1949
        mod_list.append(node)
1950
        node.master_candidate = True
1951
        node.serial_no += 1
1952
        mc_now += 1
1953
      if mc_now != mc_max:
1954
        # this should not happen
1955
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1956
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1957
      if mod_list:
1958
        self._config_data.cluster.serial_no += 1
1959
        self._WriteConfig()
1960

    
1961
    return mod_list
1962

    
1963
  def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1964
    """Add a given node to the specified group.
1965

1966
    """
1967
    if nodegroup_uuid not in self._config_data.nodegroups:
1968
      # This can happen if a node group gets deleted between its lookup and
1969
      # when we're adding the first node to it, since we don't keep a lock in
1970
      # the meantime. It's ok though, as we'll fail cleanly if the node group
1971
      # is not found anymore.
1972
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1973
    if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1974
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1975

    
1976
  def _UnlockedRemoveNodeFromGroup(self, node):
1977
    """Remove a given node from its group.
1978

1979
    """
1980
    nodegroup = node.group
1981
    if nodegroup not in self._config_data.nodegroups:
1982
      logging.warning("Warning: node '%s' has unknown node group '%s'"
1983
                      " (while being removed from it)", node.name, nodegroup)
1984
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
1985
    if node.name not in nodegroup_obj.members:
1986
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
1987
                      " (while being removed from it)", node.name, nodegroup)
1988
    else:
1989
      nodegroup_obj.members.remove(node.name)
1990

    
1991
  @locking.ssynchronized(_config_lock)
1992
  def AssignGroupNodes(self, mods):
1993
    """Changes the group of a number of nodes.
1994

1995
    @type mods: list of tuples; (node name, new group UUID)
1996
    @param mods: Node membership modifications
1997

1998
    """
1999
    groups = self._config_data.nodegroups
2000
    nodes = self._config_data.nodes
2001

    
2002
    resmod = []
2003

    
2004
    # Try to resolve names/UUIDs first
2005
    for (node_name, new_group_uuid) in mods:
2006
      try:
2007
        node = nodes[node_name]
2008
      except KeyError:
2009
        raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
2010

    
2011
      if node.group == new_group_uuid:
2012
        # Node is being assigned to its current group
2013
        logging.debug("Node '%s' was assigned to its current group (%s)",
2014
                      node_name, node.group)
2015
        continue
2016

    
2017
      # Try to find current group of node
2018
      try:
2019
        old_group = groups[node.group]
2020
      except KeyError:
2021
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2022
                                        node.group)
2023

    
2024
      # Try to find new group for node
2025
      try:
2026
        new_group = groups[new_group_uuid]
2027
      except KeyError:
2028
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2029
                                        new_group_uuid)
2030

    
2031
      assert node.name in old_group.members, \
2032
        ("Inconsistent configuration: node '%s' not listed in members for its"
2033
         " old group '%s'" % (node.name, old_group.uuid))
2034
      assert node.name not in new_group.members, \
2035
        ("Inconsistent configuration: node '%s' already listed in members for"
2036
         " its new group '%s'" % (node.name, new_group.uuid))
2037

    
2038
      resmod.append((node, old_group, new_group))
2039

    
2040
    # Apply changes
2041
    for (node, old_group, new_group) in resmod:
2042
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2043
        "Assigning to current group is not possible"
2044

    
2045
      node.group = new_group.uuid
2046

    
2047
      # Update members of involved groups
2048
      if node.name in old_group.members:
2049
        old_group.members.remove(node.name)
2050
      if node.name not in new_group.members:
2051
        new_group.members.append(node.name)
2052

    
2053
    # Update timestamps and serials (only once per node/group object)
2054
    now = time.time()
2055
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2056
      obj.serial_no += 1
2057
      obj.mtime = now
2058

    
2059
    # Force ssconf update
2060
    self._config_data.cluster.serial_no += 1
2061

    
2062
    self._WriteConfig()
2063

    
2064
  def _BumpSerialNo(self):
2065
    """Bump up the serial number of the config.
2066

2067
    """
2068
    self._config_data.serial_no += 1
2069
    self._config_data.mtime = time.time()
2070

    
2071
  def _AllUUIDObjects(self):
2072
    """Returns all objects with uuid attributes.
2073

2074
    """
2075
    return (self._config_data.instances.values() +
2076
            self._config_data.nodes.values() +
2077
            self._config_data.nodegroups.values() +
2078
            self._config_data.networks.values() +
2079
            self._AllDisks() +
2080
            self._AllNICs() +
2081
            [self._config_data.cluster])
2082

    
2083
  def _OpenConfig(self, accept_foreign):
2084
    """Read the config data from disk.
2085

2086
    """
2087
    raw_data = utils.ReadFile(self._cfg_file)
2088

    
2089
    try:
2090
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2091
    except Exception, err:
2092
      raise errors.ConfigurationError(err)
2093

    
2094
    # Make sure the configuration has the right version
2095
    _ValidateConfig(data)
2096

    
2097
    if (not hasattr(data, "cluster") or
2098
        not hasattr(data.cluster, "rsahostkeypub")):
2099
      raise errors.ConfigurationError("Incomplete configuration"
2100
                                      " (missing cluster.rsahostkeypub)")
2101

    
2102
    if data.cluster.master_node != self._my_hostname and not accept_foreign:
2103
      msg = ("The configuration denotes node %s as master, while my"
2104
             " hostname is %s; opening a foreign configuration is only"
2105
             " possible in accept_foreign mode" %
2106
             (data.cluster.master_node, self._my_hostname))
2107
      raise errors.ConfigurationError(msg)
2108

    
2109
    self._config_data = data
2110
    # reset the last serial as -1 so that the next write will cause
2111
    # ssconf update
2112
    self._last_cluster_serial = -1
2113

    
2114
    # Upgrade configuration if needed
2115
    self._UpgradeConfig()
2116

    
2117
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2118

    
2119
  def _UpgradeConfig(self):
2120
    """Run any upgrade steps.
2121

2122
    This method performs both in-object upgrades and also update some data
2123
    elements that need uniqueness across the whole configuration or interact
2124
    with other objects.
2125

2126
    @warning: this function will call L{_WriteConfig()}, but also
2127
        L{DropECReservations} so it needs to be called only from a
2128
        "safe" place (the constructor). If one wanted to call it with
2129
        the lock held, a DropECReservationUnlocked would need to be
2130
        created first, to avoid causing deadlock.
2131

2132
    """
2133
    # Keep a copy of the persistent part of _config_data to check for changes
2134
    # Serialization doesn't guarantee order in dictionaries
2135
    oldconf = copy.deepcopy(self._config_data.ToDict())
2136

    
2137
    # In-object upgrades
2138
    self._config_data.UpgradeConfig()
2139

    
2140
    for item in self._AllUUIDObjects():
2141
      if item.uuid is None:
2142
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2143
    if not self._config_data.nodegroups:
2144
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2145
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2146
                                            members=[])
2147
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2148
    for node in self._config_data.nodes.values():
2149
      if not node.group:
2150
        node.group = self.LookupNodeGroup(None)
2151
      # This is technically *not* an upgrade, but needs to be done both when
2152
      # nodegroups are being added, and upon normally loading the config,
2153
      # because the members list of a node group is discarded upon
2154
      # serializing/deserializing the object.
2155
      self._UnlockedAddNodeToGroup(node.name, node.group)
2156

    
2157
    modified = (oldconf != self._config_data.ToDict())
2158
    if modified:
2159
      self._WriteConfig()
2160
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2161
      # only called at config init time, without the lock held
2162
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2163
    else:
2164
      config_errors = self._UnlockedVerifyConfig()
2165
      if config_errors:
2166
        errmsg = ("Loaded configuration data is not consistent: %s" %
2167
                  (utils.CommaJoin(config_errors)))
2168
        logging.critical(errmsg)
2169

    
2170
  def _DistributeConfig(self, feedback_fn):
2171
    """Distribute the configuration to the other nodes.
2172

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

2176
    """
2177
    if self._offline:
2178
      return True
2179

    
2180
    bad = False
2181

    
2182
    node_list = []
2183
    addr_list = []
2184
    myhostname = self._my_hostname
2185
    # we can skip checking whether _UnlockedGetNodeInfo returns None
2186
    # since the node list comes from _UnlocketGetNodeList, and we are
2187
    # called with the lock held, so no modifications should take place
2188
    # in between
2189
    for node_name in self._UnlockedGetNodeList():
2190
      if node_name == myhostname:
2191
        continue
2192
      node_info = self._UnlockedGetNodeInfo(node_name)
2193
      if not node_info.master_candidate:
2194
        continue
2195
      node_list.append(node_info.name)
2196
      addr_list.append(node_info.primary_ip)
2197

    
2198
    # TODO: Use dedicated resolver talking to config writer for name resolution
2199
    result = \
2200
      self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2201
    for to_node, to_result in result.items():
2202
      msg = to_result.fail_msg
2203
      if msg:
2204
        msg = ("Copy of file %s to node %s failed: %s" %
2205
               (self._cfg_file, to_node, msg))
2206
        logging.error(msg)
2207

    
2208
        if feedback_fn:
2209
          feedback_fn(msg)
2210

    
2211
        bad = True
2212

    
2213
    return not bad
2214

    
2215
  def _WriteConfig(self, destination=None, feedback_fn=None):
2216
    """Write the configuration data to persistent storage.
2217

2218
    """
2219
    assert feedback_fn is None or callable(feedback_fn)
2220

    
2221
    # Warn on config errors, but don't abort the save - the
2222
    # configuration has already been modified, and we can't revert;
2223
    # the best we can do is to warn the user and save as is, leaving
2224
    # recovery to the user
2225
    config_errors = self._UnlockedVerifyConfig()
2226
    if config_errors:
2227
      errmsg = ("Configuration data is not consistent: %s" %
2228
                (utils.CommaJoin(config_errors)))
2229
      logging.critical(errmsg)
2230
      if feedback_fn:
2231
        feedback_fn(errmsg)
2232

    
2233
    if destination is None:
2234
      destination = self._cfg_file
2235
    self._BumpSerialNo()
2236
    txt = serializer.Dump(self._config_data.ToDict())
2237

    
2238
    getents = self._getents()
2239
    try:
2240
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2241
                               close=False, gid=getents.confd_gid, mode=0640)
2242
    except errors.LockError:
2243
      raise errors.ConfigurationError("The configuration file has been"
2244
                                      " modified since the last write, cannot"
2245
                                      " update")
2246
    try:
2247
      self._cfg_id = utils.GetFileID(fd=fd)
2248
    finally:
2249
      os.close(fd)
2250

    
2251
    self.write_count += 1
2252

    
2253
    # and redistribute the config file to master candidates
2254
    self._DistributeConfig(feedback_fn)
2255

    
2256
    # Write ssconf files on all nodes (including locally)
2257
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
2258
      if not self._offline:
2259
        result = self._GetRpc(None).call_write_ssconf_files(
2260
          self._UnlockedGetOnlineNodeList(),
2261
          self._UnlockedGetSsconfValues())
2262

    
2263
        for nname, nresu in result.items():
2264
          msg = nresu.fail_msg
2265
          if msg:
2266
            errmsg = ("Error while uploading ssconf files to"
2267
                      " node %s: %s" % (nname, msg))
2268
            logging.warning(errmsg)
2269

    
2270
            if feedback_fn:
2271
              feedback_fn(errmsg)
2272

    
2273
      self._last_cluster_serial = self._config_data.cluster.serial_no
2274

    
2275
  def _UnlockedGetSsconfValues(self):
2276
    """Return the values needed by ssconf.
2277

2278
    @rtype: dict
2279
    @return: a dictionary with keys the ssconf names and values their
2280
        associated value
2281

2282
    """
2283
    fn = "\n".join
2284
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2285
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
2286
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
2287
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2288
                    for ninfo in node_info]
2289
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2290
                    for ninfo in node_info]
2291

    
2292
    instance_data = fn(instance_names)
2293
    off_data = fn(node.name for node in node_info if node.offline)
2294
    on_data = fn(node.name for node in node_info if not node.offline)
2295
    mc_data = fn(node.name for node in node_info if node.master_candidate)
2296
    mc_ips_data = fn(node.primary_ip for node in node_info
2297
                     if node.master_candidate)
2298
    node_data = fn(node_names)
2299
    node_pri_ips_data = fn(node_pri_ips)
2300
    node_snd_ips_data = fn(node_snd_ips)
2301

    
2302
    cluster = self._config_data.cluster
2303
    cluster_tags = fn(cluster.GetTags())
2304

    
2305
    hypervisor_list = fn(cluster.enabled_hypervisors)
2306

    
2307
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2308

    
2309
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2310
                  self._config_data.nodegroups.values()]
2311
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2312
    networks = ["%s %s" % (net.uuid, net.name) for net in
2313
                self._config_data.networks.values()]
2314
    networks_data = fn(utils.NiceSort(networks))
2315

    
2316
    ssconf_values = {
2317
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
2318
      constants.SS_CLUSTER_TAGS: cluster_tags,
2319
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2320
      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2321
      constants.SS_MASTER_CANDIDATES: mc_data,
2322
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2323
      constants.SS_MASTER_IP: cluster.master_ip,
2324
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
2325
      constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2326
      constants.SS_MASTER_NODE: cluster.master_node,
2327
      constants.SS_NODE_LIST: node_data,
2328
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2329
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2330
      constants.SS_OFFLINE_NODES: off_data,
2331
      constants.SS_ONLINE_NODES: on_data,
2332
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2333
      constants.SS_INSTANCE_LIST: instance_data,
2334
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2335
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
2336
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2337
      constants.SS_UID_POOL: uid_pool,
2338
      constants.SS_NODEGROUPS: nodegroups_data,
2339
      constants.SS_NETWORKS: networks_data,
2340
      }
2341
    bad_values = [(k, v) for k, v in ssconf_values.items()
2342
                  if not isinstance(v, (str, basestring))]
2343
    if bad_values:
2344
      err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2345
      raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2346
                                      " values: %s" % err)
2347
    return ssconf_values
2348

    
2349
  @locking.ssynchronized(_config_lock, shared=1)
2350
  def GetSsconfValues(self):
2351
    """Wrapper using lock around _UnlockedGetSsconf().
2352

2353
    """
2354
    return self._UnlockedGetSsconfValues()
2355

    
2356
  @locking.ssynchronized(_config_lock, shared=1)
2357
  def GetVGName(self):
2358
    """Return the volume group name.
2359

2360
    """
2361
    return self._config_data.cluster.volume_group_name
2362

    
2363
  @locking.ssynchronized(_config_lock)
2364
  def SetVGName(self, vg_name):
2365
    """Set the volume group name.
2366

2367
    """
2368
    self._config_data.cluster.volume_group_name = vg_name
2369
    self._config_data.cluster.serial_no += 1
2370
    self._WriteConfig()
2371

    
2372
  @locking.ssynchronized(_config_lock, shared=1)
2373
  def GetDRBDHelper(self):
2374
    """Return DRBD usermode helper.
2375

2376
    """
2377
    return self._config_data.cluster.drbd_usermode_helper
2378

    
2379
  @locking.ssynchronized(_config_lock)
2380
  def SetDRBDHelper(self, drbd_helper):
2381
    """Set DRBD usermode helper.
2382

2383
    """
2384
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2385
    self._config_data.cluster.serial_no += 1
2386
    self._WriteConfig()
2387

    
2388
  @locking.ssynchronized(_config_lock, shared=1)
2389
  def GetMACPrefix(self):
2390
    """Return the mac prefix.
2391

2392
    """
2393
    return self._config_data.cluster.mac_prefix
2394

    
2395
  @locking.ssynchronized(_config_lock, shared=1)
2396
  def GetClusterInfo(self):
2397
    """Returns information about the cluster
2398

2399
    @rtype: L{objects.Cluster}
2400
    @return: the cluster object
2401

2402
    """
2403
    return self._config_data.cluster
2404

    
2405
  @locking.ssynchronized(_config_lock, shared=1)
2406
  def HasAnyDiskOfType(self, dev_type):
2407
    """Check if in there is at disk of the given type in the configuration.
2408

2409
    """
2410
    return self._config_data.HasAnyDiskOfType(dev_type)
2411

    
2412
  @locking.ssynchronized(_config_lock)
2413
  def Update(self, target, feedback_fn, ec_id=None):
2414
    """Notify function to be called after updates.
2415

2416
    This function must be called when an object (as returned by
2417
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2418
    caller wants the modifications saved to the backing store. Note
2419
    that all modified objects will be saved, but the target argument
2420
    is the one the caller wants to ensure that it's saved.
2421

2422
    @param target: an instance of either L{objects.Cluster},
2423
        L{objects.Node} or L{objects.Instance} which is existing in
2424
        the cluster
2425
    @param feedback_fn: Callable feedback function
2426

2427
    """
2428
    if self._config_data is None:
2429
      raise errors.ProgrammerError("Configuration file not read,"
2430
                                   " cannot save.")
2431
    update_serial = False
2432
    if isinstance(target, objects.Cluster):
2433
      test = target == self._config_data.cluster
2434
    elif isinstance(target, objects.Node):
2435
      test = target in self._config_data.nodes.values()
2436
      update_serial = True
2437
    elif isinstance(target, objects.Instance):
2438
      test = target in self._config_data.instances.values()
2439
    elif isinstance(target, objects.NodeGroup):
2440
      test = target in self._config_data.nodegroups.values()
2441
    elif isinstance(target, objects.Network):
2442
      test = target in self._config_data.networks.values()
2443
    else:
2444
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2445
                                   " ConfigWriter.Update" % type(target))
2446
    if not test:
2447
      raise errors.ConfigurationError("Configuration updated since object"
2448
                                      " has been read or unknown object")
2449
    target.serial_no += 1
2450
    target.mtime = now = time.time()
2451

    
2452
    if update_serial:
2453
      # for node updates, we need to increase the cluster serial too
2454
      self._config_data.cluster.serial_no += 1
2455
      self._config_data.cluster.mtime = now
2456

    
2457
    if isinstance(target, objects.Instance):
2458
      self._UnlockedReleaseDRBDMinors(target.name)
2459

    
2460
    self._UnlockedCommitTemporaryIps(ec_id)
2461

    
2462
    self._WriteConfig(feedback_fn=feedback_fn)
2463

    
2464
  @locking.ssynchronized(_config_lock)
2465
  def DropECReservations(self, ec_id):
2466
    """Drop per-execution-context reservations
2467

2468
    """
2469
    for rm in self._all_rms:
2470
      rm.DropECReservations(ec_id)
2471

    
2472
  @locking.ssynchronized(_config_lock, shared=1)
2473
  def GetAllNetworksInfo(self):
2474
    """Get configuration info of all the networks.
2475

2476
    """
2477
    return dict(self._config_data.networks)
2478

    
2479
  def _UnlockedGetNetworkList(self):
2480
    """Get the list of networks.
2481

2482
    This function is for internal use, when the config lock is already held.
2483

2484
    """
2485
    return self._config_data.networks.keys()
2486

    
2487
  @locking.ssynchronized(_config_lock, shared=1)
2488
  def GetNetworkList(self):
2489
    """Get the list of networks.
2490

2491
    @return: array of networks, ex. ["main", "vlan100", "200]
2492

2493
    """
2494
    return self._UnlockedGetNetworkList()
2495

    
2496
  @locking.ssynchronized(_config_lock, shared=1)
2497
  def GetNetworkNames(self):
2498
    """Get a list of network names
2499

2500
    """
2501
    names = [net.name
2502
             for net in self._config_data.networks.values()]
2503
    return names
2504

    
2505
  def _UnlockedGetNetwork(self, uuid):
2506
    """Returns information about a network.
2507

2508
    This function is for internal use, when the config lock is already held.
2509

2510
    """
2511
    if uuid not in self._config_data.networks:
2512
      return None
2513

    
2514
    return self._config_data.networks[uuid]
2515

    
2516
  @locking.ssynchronized(_config_lock, shared=1)
2517
  def GetNetwork(self, uuid):
2518
    """Returns information about a network.
2519

2520
    It takes the information from the configuration file.
2521

2522
    @param uuid: UUID of the network
2523

2524
    @rtype: L{objects.Network}
2525
    @return: the network object
2526

2527
    """
2528
    return self._UnlockedGetNetwork(uuid)
2529

    
2530
  @locking.ssynchronized(_config_lock)
2531
  def AddNetwork(self, net, ec_id, check_uuid=True):
2532
    """Add a network to the configuration.
2533

2534
    @type net: L{objects.Network}
2535
    @param net: the Network object to add
2536
    @type ec_id: string
2537
    @param ec_id: unique id for the job to use when creating a missing UUID
2538

2539
    """
2540
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2541
    self._WriteConfig()
2542

    
2543
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2544
    """Add a network to the configuration.
2545

2546
    """
2547
    logging.info("Adding network %s to configuration", net.name)
2548

    
2549
    if check_uuid:
2550
      self._EnsureUUID(net, ec_id)
2551

    
2552
    net.serial_no = 1
2553
    self._config_data.networks[net.uuid] = net
2554
    self._config_data.cluster.serial_no += 1
2555

    
2556
  def _UnlockedLookupNetwork(self, target):
2557
    """Lookup a network's UUID.
2558

2559
    @type target: string
2560
    @param target: network name or UUID
2561
    @rtype: string
2562
    @return: network UUID
2563
    @raises errors.OpPrereqError: when the target network cannot be found
2564

2565
    """
2566
    if target is None:
2567
      return None
2568
    if target in self._config_data.networks:
2569
      return target
2570
    for net in self._config_data.networks.values():
2571
      if net.name == target:
2572
        return net.uuid
2573
    raise errors.OpPrereqError("Network '%s' not found" % target,
2574
                               errors.ECODE_NOENT)
2575

    
2576
  @locking.ssynchronized(_config_lock, shared=1)
2577
  def LookupNetwork(self, target):
2578
    """Lookup a network's UUID.
2579

2580
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2581

2582
    @type target: string
2583
    @param target: network name or UUID
2584
    @rtype: string
2585
    @return: network UUID
2586

2587
    """
2588
    return self._UnlockedLookupNetwork(target)
2589

    
2590
  @locking.ssynchronized(_config_lock)
2591
  def RemoveNetwork(self, network_uuid):
2592
    """Remove a network from the configuration.
2593

2594
    @type network_uuid: string
2595
    @param network_uuid: the UUID of the network to remove
2596

2597
    """
2598
    logging.info("Removing network %s from configuration", network_uuid)
2599

    
2600
    if network_uuid not in self._config_data.networks:
2601
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2602

    
2603
    del self._config_data.networks[network_uuid]
2604
    self._config_data.cluster.serial_no += 1
2605
    self._WriteConfig()
2606

    
2607
  def _UnlockedGetGroupNetParams(self, net_uuid, node):
2608
    """Get the netparams (mode, link) of a network.
2609

2610
    Get a network's netparams for a given node.
2611

2612
    @type net_uuid: string
2613
    @param net_uuid: network uuid
2614
    @type node: string
2615
    @param node: node name
2616
    @rtype: dict or None
2617
    @return: netparams
2618

2619
    """
2620
    node_info = self._UnlockedGetNodeInfo(node)
2621
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2622
    netparams = nodegroup_info.networks.get(net_uuid, None)
2623

    
2624
    return netparams
2625

    
2626
  @locking.ssynchronized(_config_lock, shared=1)
2627
  def GetGroupNetParams(self, net_uuid, node):
2628
    """Locking wrapper of _UnlockedGetGroupNetParams()
2629

2630
    """
2631
    return self._UnlockedGetGroupNetParams(net_uuid, node)
2632

    
2633
  @locking.ssynchronized(_config_lock, shared=1)
2634
  def CheckIPInNodeGroup(self, ip, node):
2635
    """Check IP uniqueness in nodegroup.
2636

2637
    Check networks that are connected in the node's node group
2638
    if ip is contained in any of them. Used when creating/adding
2639
    a NIC to ensure uniqueness among nodegroups.
2640

2641
    @type ip: string
2642
    @param ip: ip address
2643
    @type node: string
2644
    @param node: node name
2645
    @rtype: (string, dict) or (None, None)
2646
    @return: (network name, netparams)
2647

2648
    """
2649
    if ip is None:
2650
      return (None, None)
2651
    node_info = self._UnlockedGetNodeInfo(node)
2652
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2653
    for net_uuid in nodegroup_info.networks.keys():
2654
      nobj = self._UnlockedGetNetwork(net_uuid)
2655
      pool = network.Network(nobj)
2656
      if pool.Contains(ip):
2657
        return (nobj.name, nodegroup_info.networks[net_uuid])
2658

    
2659
    return (None, None)