Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ bbfa259c

History | View | Annotate | Download (86.7 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 in self._temporary_ips.GetECReserved(ec_id):
334
      self._UnlockedCommitIp(action, net_uuid, address)
335

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

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

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

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

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

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

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

363
    This is just a wrapper around _UnlockedReleaseIp.
364

365
    """
366
    if net_uuid:
367
      self._UnlockedReleaseIp(net_uuid, address, ec_id)
368

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

373
    """
374
    nobj = self._UnlockedGetNetwork(net_uuid)
375
    pool = network.AddressPool(nobj)
376

    
377
    def gen_one():
378
      try:
379
        ip = pool.GenerateFree()
380
      except errors.AddressPoolError:
381
        raise errors.ReservationError("Cannot generate IP. Network is full")
382
      return (constants.RESERVE_ACTION, ip, net_uuid)
383

    
384
    _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
385
    return address
386

    
387
  def _UnlockedReserveIp(self, net_uuid, address, ec_id):
388
    """Reserve a given IPv4 address for use by an instance.
389

390
    """
391
    nobj = self._UnlockedGetNetwork(net_uuid)
392
    pool = network.AddressPool(nobj)
393
    try:
394
      isreserved = pool.IsReserved(address)
395
    except errors.AddressPoolError:
396
      raise errors.ReservationError("IP address not in network")
397
    if isreserved:
398
      raise errors.ReservationError("IP address already in use")
399

    
400
    return self._temporary_ips.Reserve(ec_id,
401
                                       (constants.RESERVE_ACTION,
402
                                        address, net_uuid))
403

    
404
  @locking.ssynchronized(_config_lock, shared=1)
405
  def ReserveIp(self, net_uuid, address, ec_id):
406
    """Reserve a given IPv4 address for use by an instance.
407

408
    """
409
    if net_uuid:
410
      return self._UnlockedReserveIp(net_uuid, address, ec_id)
411

    
412
  @locking.ssynchronized(_config_lock, shared=1)
413
  def ReserveLV(self, lv_name, ec_id):
414
    """Reserve an VG/LV pair for an instance.
415

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

419
    """
420
    all_lvs = self._AllLVs()
421
    if lv_name in all_lvs:
422
      raise errors.ReservationError("LV already in use")
423
    else:
424
      self._temporary_lvs.Reserve(ec_id, lv_name)
425

    
426
  @locking.ssynchronized(_config_lock, shared=1)
427
  def GenerateDRBDSecret(self, ec_id):
428
    """Generate a DRBD secret.
429

430
    This checks the current disks for duplicates.
431

432
    """
433
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
434
                                            utils.GenerateSecret,
435
                                            ec_id)
436

    
437
  def _AllLVs(self):
438
    """Compute the list of all LVs.
439

440
    """
441
    lvnames = set()
442
    for instance in self._config_data.instances.values():
443
      node_data = instance.MapLVsByNode()
444
      for lv_list in node_data.values():
445
        lvnames.update(lv_list)
446
    return lvnames
447

    
448
  def _AllDisks(self):
449
    """Compute the list of all Disks.
450

451
    """
452
    disks = []
453
    for instance in self._config_data.instances.values():
454
      disks.extend(instance.disks)
455
    return disks
456

    
457
  def _AllNICs(self):
458
    """Compute the list of all NICs.
459

460
    """
461
    nics = []
462
    for instance in self._config_data.instances.values():
463
      nics.extend(instance.nics)
464
    return nics
465

    
466
  def _AllIDs(self, include_temporary):
467
    """Compute the list of all UUIDs and names we have.
468

469
    @type include_temporary: boolean
470
    @param include_temporary: whether to include the _temporary_ids set
471
    @rtype: set
472
    @return: a set of IDs
473

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

    
484
  def _GenerateUniqueID(self, ec_id):
485
    """Generate an unique UUID.
486

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

490
    @rtype: string
491
    @return: the unique id
492

493
    """
494
    existing = self._AllIDs(include_temporary=False)
495
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
496

    
497
  @locking.ssynchronized(_config_lock, shared=1)
498
  def GenerateUniqueID(self, ec_id):
499
    """Generate an unique ID.
500

501
    This is just a wrapper over the unlocked version.
502

503
    @type ec_id: string
504
    @param ec_id: unique id for the job to reserve the id to
505

506
    """
507
    return self._GenerateUniqueID(ec_id)
508

    
509
  def _AllMACs(self):
510
    """Return all MACs present in the config.
511

512
    @rtype: list
513
    @return: the list of all MACs
514

515
    """
516
    result = []
517
    for instance in self._config_data.instances.values():
518
      for nic in instance.nics:
519
        result.append(nic.mac)
520

    
521
    return result
522

    
523
  def _AllDRBDSecrets(self):
524
    """Return all DRBD secrets present in the config.
525

526
    @rtype: list
527
    @return: the list of all DRBD secrets
528

529
    """
530
    def helper(disk, result):
531
      """Recursively gather secrets from this disk."""
532
      if disk.dev_type == constants.DT_DRBD8:
533
        result.append(disk.logical_id[5])
534
      if disk.children:
535
        for child in disk.children:
536
          helper(child, result)
537

    
538
    result = []
539
    for instance in self._config_data.instances.values():
540
      for disk in instance.disks:
541
        helper(disk, result)
542

    
543
    return result
544

    
545
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
546
    """Compute duplicate disk IDs
547

548
    @type disk: L{objects.Disk}
549
    @param disk: the disk at which to start searching
550
    @type l_ids: list
551
    @param l_ids: list of current logical ids
552
    @type p_ids: list
553
    @param p_ids: list of current physical ids
554
    @rtype: list
555
    @return: a list of error messages
556

557
    """
558
    result = []
559
    if disk.logical_id is not None:
560
      if disk.logical_id in l_ids:
561
        result.append("duplicate logical id %s" % str(disk.logical_id))
562
      else:
563
        l_ids.append(disk.logical_id)
564
    if disk.physical_id is not None:
565
      if disk.physical_id in p_ids:
566
        result.append("duplicate physical id %s" % str(disk.physical_id))
567
      else:
568
        p_ids.append(disk.physical_id)
569

    
570
    if disk.children:
571
      for child in disk.children:
572
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
573
    return result
574

    
575
  def _UnlockedVerifyConfig(self):
576
    """Verify function.
577

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

582
    """
583
    # pylint: disable=R0914
584
    result = []
585
    seen_macs = []
586
    ports = {}
587
    data = self._config_data
588
    cluster = data.cluster
589
    seen_lids = []
590
    seen_pids = []
591

    
592
    # global cluster checks
593
    if not cluster.enabled_hypervisors:
594
      result.append("enabled hypervisors list doesn't have any entries")
595
    invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
596
    if invalid_hvs:
597
      result.append("enabled hypervisors contains invalid entries: %s" %
598
                    utils.CommaJoin(invalid_hvs))
599
    missing_hvp = (set(cluster.enabled_hypervisors) -
600
                   set(cluster.hvparams.keys()))
601
    if missing_hvp:
602
      result.append("hypervisor parameters missing for the enabled"
603
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
604

    
605
    if not cluster.enabled_disk_templates:
606
      result.append("enabled disk templates list doesn't have any entries")
607
    invalid_disk_templates = set(cluster.enabled_disk_templates) \
608
                               - constants.DISK_TEMPLATES
609
    if invalid_disk_templates:
610
      result.append("enabled disk templates list contains invalid entries:"
611
                    " %s" % utils.CommaJoin(invalid_disk_templates))
612

    
613
    if cluster.master_node not in data.nodes:
614
      result.append("cluster has invalid primary node '%s'" %
615
                    cluster.master_node)
616

    
617
    def _helper(owner, attr, value, template):
618
      try:
619
        utils.ForceDictType(value, template)
620
      except errors.GenericError, err:
621
        result.append("%s has invalid %s: %s" % (owner, attr, err))
622

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

    
629
    def _helper_ipolicy(owner, ipolicy, iscluster):
630
      try:
631
        objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
632
      except errors.ConfigurationError, err:
633
        result.append("%s has invalid instance policy: %s" % (owner, err))
634
      for key, value in ipolicy.items():
635
        if key == constants.ISPECS_MINMAX:
636
          _helper_ispecs(owner, "ipolicy/" + key, value)
637
        elif key == constants.ISPECS_STD:
638
          _helper(owner, "ipolicy/" + key, value,
639
                  constants.ISPECS_PARAMETER_TYPES)
640
        else:
641
          # FIXME: assuming list type
642
          if key in constants.IPOLICY_PARAMETERS:
643
            exp_type = float
644
          else:
645
            exp_type = list
646
          if not isinstance(value, exp_type):
647
            result.append("%s has invalid instance policy: for %s,"
648
                          " expecting %s, got %s" %
649
                          (owner, key, exp_type.__name__, type(value)))
650

    
651
    def _helper_ispecs(owner, parentkey, params):
652
      for (key, value) in params.items():
653
        fullkey = "/".join([parentkey, key])
654
        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
655

    
656
    # check cluster parameters
657
    _helper("cluster", "beparams", cluster.SimpleFillBE({}),
658
            constants.BES_PARAMETER_TYPES)
659
    _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
660
            constants.NICS_PARAMETER_TYPES)
661
    _helper_nic("cluster", cluster.SimpleFillNIC({}))
662
    _helper("cluster", "ndparams", cluster.SimpleFillND({}),
663
            constants.NDS_PARAMETER_TYPES)
664
    _helper_ipolicy("cluster", cluster.ipolicy, True)
665

    
666
    # per-instance checks
667
    for instance_name in data.instances:
668
      instance = data.instances[instance_name]
669
      if instance.name != instance_name:
670
        result.append("instance '%s' is indexed by wrong name '%s'" %
671
                      (instance.name, instance_name))
672
      if instance.primary_node not in data.nodes:
673
        result.append("instance '%s' has invalid primary node '%s'" %
674
                      (instance_name, instance.primary_node))
675
      for snode in instance.secondary_nodes:
676
        if snode not in data.nodes:
677
          result.append("instance '%s' has invalid secondary node '%s'" %
678
                        (instance_name, snode))
679
      for idx, nic in enumerate(instance.nics):
680
        if nic.mac in seen_macs:
681
          result.append("instance '%s' has NIC %d mac %s duplicate" %
682
                        (instance_name, idx, nic.mac))
683
        else:
684
          seen_macs.append(nic.mac)
685
        if nic.nicparams:
686
          filled = cluster.SimpleFillNIC(nic.nicparams)
687
          owner = "instance %s nic %d" % (instance.name, idx)
688
          _helper(owner, "nicparams",
689
                  filled, constants.NICS_PARAMETER_TYPES)
690
          _helper_nic(owner, filled)
691

    
692
      # disk template checks
693
      if not instance.disk_template in data.cluster.enabled_disk_templates:
694
        result.append("instance '%s' uses the disabled disk template '%s'." %
695
                      (instance_name, instance.disk_template))
696

    
697
      # parameter checks
698
      if instance.beparams:
699
        _helper("instance %s" % instance.name, "beparams",
700
                cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
701

    
702
      # gather the drbd ports for duplicate checks
703
      for (idx, dsk) in enumerate(instance.disks):
704
        if dsk.dev_type in constants.LDS_DRBD:
705
          tcp_port = dsk.logical_id[2]
706
          if tcp_port not in ports:
707
            ports[tcp_port] = []
708
          ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
709
      # gather network port reservation
710
      net_port = getattr(instance, "network_port", None)
711
      if net_port is not None:
712
        if net_port not in ports:
713
          ports[net_port] = []
714
        ports[net_port].append((instance.name, "network port"))
715

    
716
      # instance disk verify
717
      for idx, disk in enumerate(instance.disks):
718
        result.extend(["instance '%s' disk %d error: %s" %
719
                       (instance.name, idx, msg) for msg in disk.Verify()])
720
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
721

    
722
      wrong_names = _CheckInstanceDiskIvNames(instance.disks)
723
      if wrong_names:
724
        tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
725
                         (idx, exp_name, actual_name))
726
                        for (idx, exp_name, actual_name) in wrong_names)
727

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

    
731
    # cluster-wide pool of free ports
732
    for free_port in cluster.tcpudp_port_pool:
733
      if free_port not in ports:
734
        ports[free_port] = []
735
      ports[free_port].append(("cluster", "port marked as free"))
736

    
737
    # compute tcp/udp duplicate ports
738
    keys = ports.keys()
739
    keys.sort()
740
    for pnum in keys:
741
      pdata = ports[pnum]
742
      if len(pdata) > 1:
743
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
744
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
745

    
746
    # highest used tcp port check
747
    if keys:
748
      if keys[-1] > cluster.highest_used_port:
749
        result.append("Highest used port mismatch, saved %s, computed %s" %
750
                      (cluster.highest_used_port, keys[-1]))
751

    
752
    if not data.nodes[cluster.master_node].master_candidate:
753
      result.append("Master node is not a master candidate")
754

    
755
    # master candidate checks
756
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
757
    if mc_now < mc_max:
758
      result.append("Not enough master candidates: actual %d, target %d" %
759
                    (mc_now, mc_max))
760

    
761
    # node checks
762
    for node_name, node in data.nodes.items():
763
      if node.name != node_name:
764
        result.append("Node '%s' is indexed by wrong name '%s'" %
765
                      (node.name, node_name))
766
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
767
        result.append("Node %s state is invalid: master_candidate=%s,"
768
                      " drain=%s, offline=%s" %
769
                      (node.name, node.master_candidate, node.drained,
770
                       node.offline))
771
      if node.group not in data.nodegroups:
772
        result.append("Node '%s' has invalid group '%s'" %
773
                      (node.name, node.group))
774
      else:
775
        _helper("node %s" % node.name, "ndparams",
776
                cluster.FillND(node, data.nodegroups[node.group]),
777
                constants.NDS_PARAMETER_TYPES)
778
      used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
779
      if used_globals:
780
        result.append("Node '%s' has some global parameters set: %s" %
781
                      (node.name, utils.CommaJoin(used_globals)))
782

    
783
    # nodegroups checks
784
    nodegroups_names = set()
785
    for nodegroup_uuid in data.nodegroups:
786
      nodegroup = data.nodegroups[nodegroup_uuid]
787
      if nodegroup.uuid != nodegroup_uuid:
788
        result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
789
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
790
      if utils.UUID_RE.match(nodegroup.name.lower()):
791
        result.append("node group '%s' (uuid: '%s') has uuid-like name" %
792
                      (nodegroup.name, nodegroup.uuid))
793
      if nodegroup.name in nodegroups_names:
794
        result.append("duplicate node group name '%s'" % nodegroup.name)
795
      else:
796
        nodegroups_names.add(nodegroup.name)
797
      group_name = "group %s" % nodegroup.name
798
      _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
799
                      False)
800
      if nodegroup.ndparams:
801
        _helper(group_name, "ndparams",
802
                cluster.SimpleFillND(nodegroup.ndparams),
803
                constants.NDS_PARAMETER_TYPES)
804

    
805
    # drbd minors check
806
    _, duplicates = self._UnlockedComputeDRBDMap()
807
    for node, minor, instance_a, instance_b in duplicates:
808
      result.append("DRBD minor %d on node %s is assigned twice to instances"
809
                    " %s and %s" % (minor, node, instance_a, instance_b))
810

    
811
    # IP checks
812
    default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
813
    ips = {}
814

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

    
818
    _AddIpAddress(cluster.master_ip, "cluster_ip")
819

    
820
    for node in data.nodes.values():
821
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
822
      if node.secondary_ip != node.primary_ip:
823
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
824

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

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

    
834
        if nic_mode == constants.NIC_MODE_BRIDGED:
835
          link = "bridge:%s" % nic_link
836
        elif nic_mode == constants.NIC_MODE_ROUTED:
837
          link = "route:%s" % nic_link
838
        else:
839
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
840

    
841
        _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
842
                      "instance:%s/nic:%d" % (instance.name, idx))
843

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

    
849
    return result
850

    
851
  @locking.ssynchronized(_config_lock, shared=1)
852
  def VerifyConfig(self):
853
    """Verify function.
854

855
    This is just a wrapper over L{_UnlockedVerifyConfig}.
856

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

861
    """
862
    return self._UnlockedVerifyConfig()
863

    
864
  def _UnlockedSetDiskID(self, disk, node_name):
865
    """Convert the unique ID to the ID needed on the target nodes.
866

867
    This is used only for drbd, which needs ip/port configuration.
868

869
    The routine descends down and updates its children also, because
870
    this helps when the only the top device is passed to the remote
871
    node.
872

873
    This function is for internal use, when the config lock is already held.
874

875
    """
876
    if disk.children:
877
      for child in disk.children:
878
        self._UnlockedSetDiskID(child, node_name)
879

    
880
    if disk.logical_id is None and disk.physical_id is not None:
881
      return
882
    if disk.dev_type == constants.LD_DRBD8:
883
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
884
      if node_name not in (pnode, snode):
885
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
886
                                        node_name)
887
      pnode_info = self._UnlockedGetNodeInfo(pnode)
888
      snode_info = self._UnlockedGetNodeInfo(snode)
889
      if pnode_info is None or snode_info is None:
890
        raise errors.ConfigurationError("Can't find primary or secondary node"
891
                                        " for %s" % str(disk))
892
      p_data = (pnode_info.secondary_ip, port)
893
      s_data = (snode_info.secondary_ip, port)
894
      if pnode == node_name:
895
        disk.physical_id = p_data + s_data + (pminor, secret)
896
      else: # it must be secondary, we tested above
897
        disk.physical_id = s_data + p_data + (sminor, secret)
898
    else:
899
      disk.physical_id = disk.logical_id
900
    return
901

    
902
  @locking.ssynchronized(_config_lock)
903
  def SetDiskID(self, disk, node_name):
904
    """Convert the unique ID to the ID needed on the target nodes.
905

906
    This is used only for drbd, which needs ip/port configuration.
907

908
    The routine descends down and updates its children also, because
909
    this helps when the only the top device is passed to the remote
910
    node.
911

912
    """
913
    return self._UnlockedSetDiskID(disk, node_name)
914

    
915
  @locking.ssynchronized(_config_lock)
916
  def AddTcpUdpPort(self, port):
917
    """Adds a new port to the available port pool.
918

919
    @warning: this method does not "flush" the configuration (via
920
        L{_WriteConfig}); callers should do that themselves once the
921
        configuration is stable
922

923
    """
924
    if not isinstance(port, int):
925
      raise errors.ProgrammerError("Invalid type passed for port")
926

    
927
    self._config_data.cluster.tcpudp_port_pool.add(port)
928

    
929
  @locking.ssynchronized(_config_lock, shared=1)
930
  def GetPortList(self):
931
    """Returns a copy of the current port list.
932

933
    """
934
    return self._config_data.cluster.tcpudp_port_pool.copy()
935

    
936
  @locking.ssynchronized(_config_lock)
937
  def AllocatePort(self):
938
    """Allocate a port.
939

940
    The port will be taken from the available port pool or from the
941
    default port range (and in this case we increase
942
    highest_used_port).
943

944
    """
945
    # If there are TCP/IP ports configured, we use them first.
946
    if self._config_data.cluster.tcpudp_port_pool:
947
      port = self._config_data.cluster.tcpudp_port_pool.pop()
948
    else:
949
      port = self._config_data.cluster.highest_used_port + 1
950
      if port >= constants.LAST_DRBD_PORT:
951
        raise errors.ConfigurationError("The highest used port is greater"
952
                                        " than %s. Aborting." %
953
                                        constants.LAST_DRBD_PORT)
954
      self._config_data.cluster.highest_used_port = port
955

    
956
    self._WriteConfig()
957
    return port
958

    
959
  def _UnlockedComputeDRBDMap(self):
960
    """Compute the used DRBD minor/nodes.
961

962
    @rtype: (dict, list)
963
    @return: dictionary of node_name: dict of minor: instance_name;
964
        the returned dict will have all the nodes in it (even if with
965
        an empty list), and a list of duplicates; if the duplicates
966
        list is not empty, the configuration is corrupted and its caller
967
        should raise an exception
968

969
    """
970
    def _AppendUsedPorts(instance_name, disk, used):
971
      duplicates = []
972
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
973
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
974
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
975
          assert node in used, ("Node '%s' of instance '%s' not found"
976
                                " in node list" % (node, instance_name))
977
          if port in used[node]:
978
            duplicates.append((node, port, instance_name, used[node][port]))
979
          else:
980
            used[node][port] = instance_name
981
      if disk.children:
982
        for child in disk.children:
983
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
984
      return duplicates
985

    
986
    duplicates = []
987
    my_dict = dict((node, {}) for node in self._config_data.nodes)
988
    for instance in self._config_data.instances.itervalues():
989
      for disk in instance.disks:
990
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
991
    for (node, minor), instance in self._temporary_drbds.iteritems():
992
      if minor in my_dict[node] and my_dict[node][minor] != instance:
993
        duplicates.append((node, minor, instance, my_dict[node][minor]))
994
      else:
995
        my_dict[node][minor] = instance
996
    return my_dict, duplicates
997

    
998
  @locking.ssynchronized(_config_lock)
999
  def ComputeDRBDMap(self):
1000
    """Compute the used DRBD minor/nodes.
1001

1002
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
1003

1004
    @return: dictionary of node_name: dict of minor: instance_name;
1005
        the returned dict will have all the nodes in it (even if with
1006
        an empty list).
1007

1008
    """
1009
    d_map, duplicates = self._UnlockedComputeDRBDMap()
1010
    if duplicates:
1011
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1012
                                      str(duplicates))
1013
    return d_map
1014

    
1015
  @locking.ssynchronized(_config_lock)
1016
  def AllocateDRBDMinor(self, nodes, instance):
1017
    """Allocate a drbd minor.
1018

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

1024
    @type instance: string
1025
    @param instance: the instance for which we allocate minors
1026

1027
    """
1028
    assert isinstance(instance, basestring), \
1029
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
1030

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

    
1071
  def _UnlockedReleaseDRBDMinors(self, instance):
1072
    """Release temporary drbd minors allocated for a given instance.
1073

1074
    @type instance: string
1075
    @param instance: the instance for which temporary minors should be
1076
                     released
1077

1078
    """
1079
    assert isinstance(instance, basestring), \
1080
           "Invalid argument passed to ReleaseDRBDMinors"
1081
    for key, name in self._temporary_drbds.items():
1082
      if name == instance:
1083
        del self._temporary_drbds[key]
1084

    
1085
  @locking.ssynchronized(_config_lock)
1086
  def ReleaseDRBDMinors(self, instance):
1087
    """Release temporary drbd minors allocated for a given instance.
1088

1089
    This should be called on the error paths, on the success paths
1090
    it's automatically called by the ConfigWriter add and update
1091
    functions.
1092

1093
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1094

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

1099
    """
1100
    self._UnlockedReleaseDRBDMinors(instance)
1101

    
1102
  @locking.ssynchronized(_config_lock, shared=1)
1103
  def GetConfigVersion(self):
1104
    """Get the configuration version.
1105

1106
    @return: Config version
1107

1108
    """
1109
    return self._config_data.version
1110

    
1111
  @locking.ssynchronized(_config_lock, shared=1)
1112
  def GetClusterName(self):
1113
    """Get cluster name.
1114

1115
    @return: Cluster name
1116

1117
    """
1118
    return self._config_data.cluster.cluster_name
1119

    
1120
  @locking.ssynchronized(_config_lock, shared=1)
1121
  def GetMasterNode(self):
1122
    """Get the hostname of the master node for this cluster.
1123

1124
    @return: Master hostname
1125

1126
    """
1127
    return self._config_data.cluster.master_node
1128

    
1129
  @locking.ssynchronized(_config_lock, shared=1)
1130
  def GetMasterIP(self):
1131
    """Get the IP of the master node for this cluster.
1132

1133
    @return: Master IP
1134

1135
    """
1136
    return self._config_data.cluster.master_ip
1137

    
1138
  @locking.ssynchronized(_config_lock, shared=1)
1139
  def GetMasterNetdev(self):
1140
    """Get the master network device for this cluster.
1141

1142
    """
1143
    return self._config_data.cluster.master_netdev
1144

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

1149
    """
1150
    return self._config_data.cluster.master_netmask
1151

    
1152
  @locking.ssynchronized(_config_lock, shared=1)
1153
  def GetUseExternalMipScript(self):
1154
    """Get flag representing whether to use the external master IP setup script.
1155

1156
    """
1157
    return self._config_data.cluster.use_external_mip_script
1158

    
1159
  @locking.ssynchronized(_config_lock, shared=1)
1160
  def GetFileStorageDir(self):
1161
    """Get the file storage dir for this cluster.
1162

1163
    """
1164
    return self._config_data.cluster.file_storage_dir
1165

    
1166
  @locking.ssynchronized(_config_lock, shared=1)
1167
  def GetSharedFileStorageDir(self):
1168
    """Get the shared file storage dir for this cluster.
1169

1170
    """
1171
    return self._config_data.cluster.shared_file_storage_dir
1172

    
1173
  @locking.ssynchronized(_config_lock, shared=1)
1174
  def GetHypervisorType(self):
1175
    """Get the hypervisor type for this cluster.
1176

1177
    """
1178
    return self._config_data.cluster.enabled_hypervisors[0]
1179

    
1180
  @locking.ssynchronized(_config_lock, shared=1)
1181
  def GetHostKey(self):
1182
    """Return the rsa hostkey from the config.
1183

1184
    @rtype: string
1185
    @return: the rsa hostkey
1186

1187
    """
1188
    return self._config_data.cluster.rsahostkeypub
1189

    
1190
  @locking.ssynchronized(_config_lock, shared=1)
1191
  def GetDefaultIAllocator(self):
1192
    """Get the default instance allocator for this cluster.
1193

1194
    """
1195
    return self._config_data.cluster.default_iallocator
1196

    
1197
  @locking.ssynchronized(_config_lock, shared=1)
1198
  def GetPrimaryIPFamily(self):
1199
    """Get cluster primary ip family.
1200

1201
    @return: primary ip family
1202

1203
    """
1204
    return self._config_data.cluster.primary_ip_family
1205

    
1206
  @locking.ssynchronized(_config_lock, shared=1)
1207
  def GetMasterNetworkParameters(self):
1208
    """Get network parameters of the master node.
1209

1210
    @rtype: L{object.MasterNetworkParameters}
1211
    @return: network parameters of the master node
1212

1213
    """
1214
    cluster = self._config_data.cluster
1215
    result = objects.MasterNetworkParameters(
1216
      name=cluster.master_node, ip=cluster.master_ip,
1217
      netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1218
      ip_family=cluster.primary_ip_family)
1219

    
1220
    return result
1221

    
1222
  @locking.ssynchronized(_config_lock)
1223
  def AddNodeGroup(self, group, ec_id, check_uuid=True):
1224
    """Add a node group to the configuration.
1225

1226
    This method calls group.UpgradeConfig() to fill any missing attributes
1227
    according to their default values.
1228

1229
    @type group: L{objects.NodeGroup}
1230
    @param group: the NodeGroup object to add
1231
    @type ec_id: string
1232
    @param ec_id: unique id for the job to use when creating a missing UUID
1233
    @type check_uuid: bool
1234
    @param check_uuid: add an UUID to the group if it doesn't have one or, if
1235
                       it does, ensure that it does not exist in the
1236
                       configuration already
1237

1238
    """
1239
    self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1240
    self._WriteConfig()
1241

    
1242
  def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1243
    """Add a node group to the configuration.
1244

1245
    """
1246
    logging.info("Adding node group %s to configuration", group.name)
1247

    
1248
    # Some code might need to add a node group with a pre-populated UUID
1249
    # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1250
    # the "does this UUID" exist already check.
1251
    if check_uuid:
1252
      self._EnsureUUID(group, ec_id)
1253

    
1254
    try:
1255
      existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1256
    except errors.OpPrereqError:
1257
      pass
1258
    else:
1259
      raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1260
                                 " node group (UUID: %s)" %
1261
                                 (group.name, existing_uuid),
1262
                                 errors.ECODE_EXISTS)
1263

    
1264
    group.serial_no = 1
1265
    group.ctime = group.mtime = time.time()
1266
    group.UpgradeConfig()
1267

    
1268
    self._config_data.nodegroups[group.uuid] = group
1269
    self._config_data.cluster.serial_no += 1
1270

    
1271
  @locking.ssynchronized(_config_lock)
1272
  def RemoveNodeGroup(self, group_uuid):
1273
    """Remove a node group from the configuration.
1274

1275
    @type group_uuid: string
1276
    @param group_uuid: the UUID of the node group to remove
1277

1278
    """
1279
    logging.info("Removing node group %s from configuration", group_uuid)
1280

    
1281
    if group_uuid not in self._config_data.nodegroups:
1282
      raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1283

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

    
1287
    del self._config_data.nodegroups[group_uuid]
1288
    self._config_data.cluster.serial_no += 1
1289
    self._WriteConfig()
1290

    
1291
  def _UnlockedLookupNodeGroup(self, target):
1292
    """Lookup a node group's UUID.
1293

1294
    @type target: string or None
1295
    @param target: group name or UUID or None to look for the default
1296
    @rtype: string
1297
    @return: nodegroup UUID
1298
    @raises errors.OpPrereqError: when the target group cannot be found
1299

1300
    """
1301
    if target is None:
1302
      if len(self._config_data.nodegroups) != 1:
1303
        raise errors.OpPrereqError("More than one node group exists. Target"
1304
                                   " group must be specified explicitly.")
1305
      else:
1306
        return self._config_data.nodegroups.keys()[0]
1307
    if target in self._config_data.nodegroups:
1308
      return target
1309
    for nodegroup in self._config_data.nodegroups.values():
1310
      if nodegroup.name == target:
1311
        return nodegroup.uuid
1312
    raise errors.OpPrereqError("Node group '%s' not found" % target,
1313
                               errors.ECODE_NOENT)
1314

    
1315
  @locking.ssynchronized(_config_lock, shared=1)
1316
  def LookupNodeGroup(self, target):
1317
    """Lookup a node group's UUID.
1318

1319
    This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1320

1321
    @type target: string or None
1322
    @param target: group name or UUID or None to look for the default
1323
    @rtype: string
1324
    @return: nodegroup UUID
1325

1326
    """
1327
    return self._UnlockedLookupNodeGroup(target)
1328

    
1329
  def _UnlockedGetNodeGroup(self, uuid):
1330
    """Lookup a node group.
1331

1332
    @type uuid: string
1333
    @param uuid: group UUID
1334
    @rtype: L{objects.NodeGroup} or None
1335
    @return: nodegroup object, or None if not found
1336

1337
    """
1338
    if uuid not in self._config_data.nodegroups:
1339
      return None
1340

    
1341
    return self._config_data.nodegroups[uuid]
1342

    
1343
  @locking.ssynchronized(_config_lock, shared=1)
1344
  def GetNodeGroup(self, uuid):
1345
    """Lookup a node group.
1346

1347
    @type uuid: string
1348
    @param uuid: group UUID
1349
    @rtype: L{objects.NodeGroup} or None
1350
    @return: nodegroup object, or None if not found
1351

1352
    """
1353
    return self._UnlockedGetNodeGroup(uuid)
1354

    
1355
  @locking.ssynchronized(_config_lock, shared=1)
1356
  def GetAllNodeGroupsInfo(self):
1357
    """Get the configuration of all node groups.
1358

1359
    """
1360
    return dict(self._config_data.nodegroups)
1361

    
1362
  @locking.ssynchronized(_config_lock, shared=1)
1363
  def GetNodeGroupList(self):
1364
    """Get a list of node groups.
1365

1366
    """
1367
    return self._config_data.nodegroups.keys()
1368

    
1369
  @locking.ssynchronized(_config_lock, shared=1)
1370
  def GetNodeGroupMembersByNodes(self, nodes):
1371
    """Get nodes which are member in the same nodegroups as the given nodes.
1372

1373
    """
1374
    ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
1375
    return frozenset(member_name
1376
                     for node_name in nodes
1377
                     for member_name in
1378
                       self._UnlockedGetNodeGroup(ngfn(node_name)).members)
1379

    
1380
  @locking.ssynchronized(_config_lock, shared=1)
1381
  def GetMultiNodeGroupInfo(self, group_uuids):
1382
    """Get the configuration of multiple node groups.
1383

1384
    @param group_uuids: List of node group UUIDs
1385
    @rtype: list
1386
    @return: List of tuples of (group_uuid, group_info)
1387

1388
    """
1389
    return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1390

    
1391
  @locking.ssynchronized(_config_lock)
1392
  def AddInstance(self, instance, ec_id):
1393
    """Add an instance to the config.
1394

1395
    This should be used after creating a new instance.
1396

1397
    @type instance: L{objects.Instance}
1398
    @param instance: the instance object
1399

1400
    """
1401
    if not isinstance(instance, objects.Instance):
1402
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
1403

    
1404
    if instance.disk_template != constants.DT_DISKLESS:
1405
      all_lvs = instance.MapLVsByNode()
1406
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1407

    
1408
    all_macs = self._AllMACs()
1409
    for nic in instance.nics:
1410
      if nic.mac in all_macs:
1411
        raise errors.ConfigurationError("Cannot add instance %s:"
1412
                                        " MAC address '%s' already in use." %
1413
                                        (instance.name, nic.mac))
1414

    
1415
    self._EnsureUUID(instance, ec_id)
1416

    
1417
    instance.serial_no = 1
1418
    instance.ctime = instance.mtime = time.time()
1419
    self._config_data.instances[instance.name] = instance
1420
    self._config_data.cluster.serial_no += 1
1421
    self._UnlockedReleaseDRBDMinors(instance.name)
1422
    self._UnlockedCommitTemporaryIps(ec_id)
1423
    self._WriteConfig()
1424

    
1425
  def _EnsureUUID(self, item, ec_id):
1426
    """Ensures a given object has a valid UUID.
1427

1428
    @param item: the instance or node to be checked
1429
    @param ec_id: the execution context id for the uuid reservation
1430

1431
    """
1432
    if not item.uuid:
1433
      item.uuid = self._GenerateUniqueID(ec_id)
1434
    elif item.uuid in self._AllIDs(include_temporary=True):
1435
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1436
                                      " in use" % (item.name, item.uuid))
1437

    
1438
  def _SetInstanceStatus(self, instance_name, status):
1439
    """Set the instance's status to a given value.
1440

1441
    """
1442
    assert status in constants.ADMINST_ALL, \
1443
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1444

    
1445
    if instance_name not in self._config_data.instances:
1446
      raise errors.ConfigurationError("Unknown instance '%s'" %
1447
                                      instance_name)
1448
    instance = self._config_data.instances[instance_name]
1449
    if instance.admin_state != status:
1450
      instance.admin_state = status
1451
      instance.serial_no += 1
1452
      instance.mtime = time.time()
1453
      self._WriteConfig()
1454

    
1455
  @locking.ssynchronized(_config_lock)
1456
  def MarkInstanceUp(self, instance_name):
1457
    """Mark the instance status to up in the config.
1458

1459
    """
1460
    self._SetInstanceStatus(instance_name, constants.ADMINST_UP)
1461

    
1462
  @locking.ssynchronized(_config_lock)
1463
  def MarkInstanceOffline(self, instance_name):
1464
    """Mark the instance status to down in the config.
1465

1466
    """
1467
    self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE)
1468

    
1469
  @locking.ssynchronized(_config_lock)
1470
  def RemoveInstance(self, instance_name):
1471
    """Remove the instance from the configuration.
1472

1473
    """
1474
    if instance_name not in self._config_data.instances:
1475
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1476

    
1477
    # If a network port has been allocated to the instance,
1478
    # return it to the pool of free ports.
1479
    inst = self._config_data.instances[instance_name]
1480
    network_port = getattr(inst, "network_port", None)
1481
    if network_port is not None:
1482
      self._config_data.cluster.tcpudp_port_pool.add(network_port)
1483

    
1484
    instance = self._UnlockedGetInstanceInfo(instance_name)
1485

    
1486
    for nic in instance.nics:
1487
      if nic.network and nic.ip:
1488
        # Return all IP addresses to the respective address pools
1489
        self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1490

    
1491
    del self._config_data.instances[instance_name]
1492
    self._config_data.cluster.serial_no += 1
1493
    self._WriteConfig()
1494

    
1495
  @locking.ssynchronized(_config_lock)
1496
  def RenameInstance(self, old_name, new_name):
1497
    """Rename an instance.
1498

1499
    This needs to be done in ConfigWriter and not by RemoveInstance
1500
    combined with AddInstance as only we can guarantee an atomic
1501
    rename.
1502

1503
    """
1504
    if old_name not in self._config_data.instances:
1505
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1506

    
1507
    # Operate on a copy to not loose instance object in case of a failure
1508
    inst = self._config_data.instances[old_name].Copy()
1509
    inst.name = new_name
1510

    
1511
    for (idx, disk) in enumerate(inst.disks):
1512
      if disk.dev_type == constants.LD_FILE:
1513
        # rename the file paths in logical and physical id
1514
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1515
        disk.logical_id = (disk.logical_id[0],
1516
                           utils.PathJoin(file_storage_dir, inst.name,
1517
                                          "disk%s" % idx))
1518
        disk.physical_id = disk.logical_id
1519

    
1520
    # Actually replace instance object
1521
    del self._config_data.instances[old_name]
1522
    self._config_data.instances[inst.name] = inst
1523

    
1524
    # Force update of ssconf files
1525
    self._config_data.cluster.serial_no += 1
1526

    
1527
    self._WriteConfig()
1528

    
1529
  @locking.ssynchronized(_config_lock)
1530
  def MarkInstanceDown(self, instance_name):
1531
    """Mark the status of an instance to down in the configuration.
1532

1533
    """
1534
    self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN)
1535

    
1536
  def _UnlockedGetInstanceList(self):
1537
    """Get the list of instances.
1538

1539
    This function is for internal use, when the config lock is already held.
1540

1541
    """
1542
    return self._config_data.instances.keys()
1543

    
1544
  @locking.ssynchronized(_config_lock, shared=1)
1545
  def GetInstanceList(self):
1546
    """Get the list of instances.
1547

1548
    @return: array of instances, ex. ['instance2.example.com',
1549
        'instance1.example.com']
1550

1551
    """
1552
    return self._UnlockedGetInstanceList()
1553

    
1554
  def ExpandInstanceName(self, short_name):
1555
    """Attempt to expand an incomplete instance name.
1556

1557
    """
1558
    # Locking is done in L{ConfigWriter.GetInstanceList}
1559
    return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1560

    
1561
  def _UnlockedGetInstanceInfo(self, instance_name):
1562
    """Returns information about an instance.
1563

1564
    This function is for internal use, when the config lock is already held.
1565

1566
    """
1567
    if instance_name not in self._config_data.instances:
1568
      return None
1569

    
1570
    return self._config_data.instances[instance_name]
1571

    
1572
  @locking.ssynchronized(_config_lock, shared=1)
1573
  def GetInstanceInfo(self, instance_name):
1574
    """Returns information about an instance.
1575

1576
    It takes the information from the configuration file. Other information of
1577
    an instance are taken from the live systems.
1578

1579
    @param instance_name: name of the instance, e.g.
1580
        I{instance1.example.com}
1581

1582
    @rtype: L{objects.Instance}
1583
    @return: the instance object
1584

1585
    """
1586
    return self._UnlockedGetInstanceInfo(instance_name)
1587

    
1588
  @locking.ssynchronized(_config_lock, shared=1)
1589
  def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1590
    """Returns set of node group UUIDs for instance's nodes.
1591

1592
    @rtype: frozenset
1593

1594
    """
1595
    instance = self._UnlockedGetInstanceInfo(instance_name)
1596
    if not instance:
1597
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1598

    
1599
    if primary_only:
1600
      nodes = [instance.primary_node]
1601
    else:
1602
      nodes = instance.all_nodes
1603

    
1604
    return frozenset(self._UnlockedGetNodeInfo(node_name).group
1605
                     for node_name in nodes)
1606

    
1607
  @locking.ssynchronized(_config_lock, shared=1)
1608
  def GetInstanceNetworks(self, instance_name):
1609
    """Returns set of network UUIDs for instance's nics.
1610

1611
    @rtype: frozenset
1612

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

    
1618
    networks = set()
1619
    for nic in instance.nics:
1620
      if nic.network:
1621
        networks.add(nic.network)
1622

    
1623
    return frozenset(networks)
1624

    
1625
  @locking.ssynchronized(_config_lock, shared=1)
1626
  def GetMultiInstanceInfo(self, instances):
1627
    """Get the configuration of multiple instances.
1628

1629
    @param instances: list of instance names
1630
    @rtype: list
1631
    @return: list of tuples (instance, instance_info), where
1632
        instance_info is what would GetInstanceInfo return for the
1633
        node, while keeping the original order
1634

1635
    """
1636
    return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1637

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

1642
    @rtype: dict
1643
    @return: dict of (instance, instance_info), where instance_info is what
1644
              would GetInstanceInfo return for the node
1645

1646
    """
1647
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1648
                    for instance in self._UnlockedGetInstanceList()])
1649
    return my_dict
1650

    
1651
  @locking.ssynchronized(_config_lock, shared=1)
1652
  def GetInstancesInfoByFilter(self, filter_fn):
1653
    """Get instance configuration with a filter.
1654

1655
    @type filter_fn: callable
1656
    @param filter_fn: Filter function receiving instance object as parameter,
1657
      returning boolean. Important: this function is called while the
1658
      configuration locks is held. It must not do any complex work or call
1659
      functions potentially leading to a deadlock. Ideally it doesn't call any
1660
      other functions and just compares instance attributes.
1661

1662
    """
1663
    return dict((name, inst)
1664
                for (name, inst) in self._config_data.instances.items()
1665
                if filter_fn(inst))
1666

    
1667
  @locking.ssynchronized(_config_lock)
1668
  def AddNode(self, node, ec_id):
1669
    """Add a node to the configuration.
1670

1671
    @type node: L{objects.Node}
1672
    @param node: a Node instance
1673

1674
    """
1675
    logging.info("Adding node %s to configuration", node.name)
1676

    
1677
    self._EnsureUUID(node, ec_id)
1678

    
1679
    node.serial_no = 1
1680
    node.ctime = node.mtime = time.time()
1681
    self._UnlockedAddNodeToGroup(node.name, node.group)
1682
    self._config_data.nodes[node.name] = node
1683
    self._config_data.cluster.serial_no += 1
1684
    self._WriteConfig()
1685

    
1686
  @locking.ssynchronized(_config_lock)
1687
  def RemoveNode(self, node_name):
1688
    """Remove a node from the configuration.
1689

1690
    """
1691
    logging.info("Removing node %s from configuration", node_name)
1692

    
1693
    if node_name not in self._config_data.nodes:
1694
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1695

    
1696
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1697
    del self._config_data.nodes[node_name]
1698
    self._config_data.cluster.serial_no += 1
1699
    self._WriteConfig()
1700

    
1701
  def ExpandNodeName(self, short_name):
1702
    """Attempt to expand an incomplete node name.
1703

1704
    """
1705
    # Locking is done in L{ConfigWriter.GetNodeList}
1706
    return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1707

    
1708
  def _UnlockedGetNodeInfo(self, node_name):
1709
    """Get the configuration of a node, as stored in the config.
1710

1711
    This function is for internal use, when the config lock is already
1712
    held.
1713

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

1716
    @rtype: L{objects.Node}
1717
    @return: the node object
1718

1719
    """
1720
    if node_name not in self._config_data.nodes:
1721
      return None
1722

    
1723
    return self._config_data.nodes[node_name]
1724

    
1725
  @locking.ssynchronized(_config_lock, shared=1)
1726
  def GetNodeInfo(self, node_name):
1727
    """Get the configuration of a node, as stored in the config.
1728

1729
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1730

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

1733
    @rtype: L{objects.Node}
1734
    @return: the node object
1735

1736
    """
1737
    return self._UnlockedGetNodeInfo(node_name)
1738

    
1739
  @locking.ssynchronized(_config_lock, shared=1)
1740
  def GetNodeInstances(self, node_name):
1741
    """Get the instances of a node, as stored in the config.
1742

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

1745
    @rtype: (list, list)
1746
    @return: a tuple with two lists: the primary and the secondary instances
1747

1748
    """
1749
    pri = []
1750
    sec = []
1751
    for inst in self._config_data.instances.values():
1752
      if inst.primary_node == node_name:
1753
        pri.append(inst.name)
1754
      if node_name in inst.secondary_nodes:
1755
        sec.append(inst.name)
1756
    return (pri, sec)
1757

    
1758
  @locking.ssynchronized(_config_lock, shared=1)
1759
  def GetNodeGroupInstances(self, uuid, primary_only=False):
1760
    """Get the instances of a node group.
1761

1762
    @param uuid: Node group UUID
1763
    @param primary_only: Whether to only consider primary nodes
1764
    @rtype: frozenset
1765
    @return: List of instance names in node group
1766

1767
    """
1768
    if primary_only:
1769
      nodes_fn = lambda inst: [inst.primary_node]
1770
    else:
1771
      nodes_fn = lambda inst: inst.all_nodes
1772

    
1773
    return frozenset(inst.name
1774
                     for inst in self._config_data.instances.values()
1775
                     for node_name in nodes_fn(inst)
1776
                     if self._UnlockedGetNodeInfo(node_name).group == uuid)
1777

    
1778
  def _UnlockedGetNodeList(self):
1779
    """Return the list of nodes which are in the configuration.
1780

1781
    This function is for internal use, when the config lock is already
1782
    held.
1783

1784
    @rtype: list
1785

1786
    """
1787
    return self._config_data.nodes.keys()
1788

    
1789
  @locking.ssynchronized(_config_lock, shared=1)
1790
  def GetNodeList(self):
1791
    """Return the list of nodes which are in the configuration.
1792

1793
    """
1794
    return self._UnlockedGetNodeList()
1795

    
1796
  def _UnlockedGetOnlineNodeList(self):
1797
    """Return the list of nodes which are online.
1798

1799
    """
1800
    all_nodes = [self._UnlockedGetNodeInfo(node)
1801
                 for node in self._UnlockedGetNodeList()]
1802
    return [node.name for node in all_nodes if not node.offline]
1803

    
1804
  @locking.ssynchronized(_config_lock, shared=1)
1805
  def GetOnlineNodeList(self):
1806
    """Return the list of nodes which are online.
1807

1808
    """
1809
    return self._UnlockedGetOnlineNodeList()
1810

    
1811
  @locking.ssynchronized(_config_lock, shared=1)
1812
  def GetVmCapableNodeList(self):
1813
    """Return the list of nodes which are not vm capable.
1814

1815
    """
1816
    all_nodes = [self._UnlockedGetNodeInfo(node)
1817
                 for node in self._UnlockedGetNodeList()]
1818
    return [node.name for node in all_nodes if node.vm_capable]
1819

    
1820
  @locking.ssynchronized(_config_lock, shared=1)
1821
  def GetNonVmCapableNodeList(self):
1822
    """Return the list of nodes which are not vm capable.
1823

1824
    """
1825
    all_nodes = [self._UnlockedGetNodeInfo(node)
1826
                 for node in self._UnlockedGetNodeList()]
1827
    return [node.name for node in all_nodes if not node.vm_capable]
1828

    
1829
  @locking.ssynchronized(_config_lock, shared=1)
1830
  def GetMultiNodeInfo(self, nodes):
1831
    """Get the configuration of multiple nodes.
1832

1833
    @param nodes: list of node names
1834
    @rtype: list
1835
    @return: list of tuples of (node, node_info), where node_info is
1836
        what would GetNodeInfo return for the node, in the original
1837
        order
1838

1839
    """
1840
    return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1841

    
1842
  @locking.ssynchronized(_config_lock, shared=1)
1843
  def GetAllNodesInfo(self):
1844
    """Get the configuration of all nodes.
1845

1846
    @rtype: dict
1847
    @return: dict of (node, node_info), where node_info is what
1848
              would GetNodeInfo return for the node
1849

1850
    """
1851
    return self._UnlockedGetAllNodesInfo()
1852

    
1853
  def _UnlockedGetAllNodesInfo(self):
1854
    """Gets configuration of all nodes.
1855

1856
    @note: See L{GetAllNodesInfo}
1857

1858
    """
1859
    return dict([(node, self._UnlockedGetNodeInfo(node))
1860
                 for node in self._UnlockedGetNodeList()])
1861

    
1862
  @locking.ssynchronized(_config_lock, shared=1)
1863
  def GetNodeGroupsFromNodes(self, nodes):
1864
    """Returns groups for a list of nodes.
1865

1866
    @type nodes: list of string
1867
    @param nodes: List of node names
1868
    @rtype: frozenset
1869

1870
    """
1871
    return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1872

    
1873
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1874
    """Get the number of current and maximum desired and possible candidates.
1875

1876
    @type exceptions: list
1877
    @param exceptions: if passed, list of nodes that should be ignored
1878
    @rtype: tuple
1879
    @return: tuple of (current, desired and possible, possible)
1880

1881
    """
1882
    mc_now = mc_should = mc_max = 0
1883
    for node in self._config_data.nodes.values():
1884
      if exceptions and node.name in exceptions:
1885
        continue
1886
      if not (node.offline or node.drained) and node.master_capable:
1887
        mc_max += 1
1888
      if node.master_candidate:
1889
        mc_now += 1
1890
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1891
    return (mc_now, mc_should, mc_max)
1892

    
1893
  @locking.ssynchronized(_config_lock, shared=1)
1894
  def GetMasterCandidateStats(self, exceptions=None):
1895
    """Get the number of current and maximum possible candidates.
1896

1897
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1898

1899
    @type exceptions: list
1900
    @param exceptions: if passed, list of nodes that should be ignored
1901
    @rtype: tuple
1902
    @return: tuple of (current, max)
1903

1904
    """
1905
    return self._UnlockedGetMasterCandidateStats(exceptions)
1906

    
1907
  @locking.ssynchronized(_config_lock)
1908
  def MaintainCandidatePool(self, exceptions):
1909
    """Try to grow the candidate pool to the desired size.
1910

1911
    @type exceptions: list
1912
    @param exceptions: if passed, list of nodes that should be ignored
1913
    @rtype: list
1914
    @return: list with the adjusted nodes (L{objects.Node} instances)
1915

1916
    """
1917
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1918
    mod_list = []
1919
    if mc_now < mc_max:
1920
      node_list = self._config_data.nodes.keys()
1921
      random.shuffle(node_list)
1922
      for name in node_list:
1923
        if mc_now >= mc_max:
1924
          break
1925
        node = self._config_data.nodes[name]
1926
        if (node.master_candidate or node.offline or node.drained or
1927
            node.name in exceptions or not node.master_capable):
1928
          continue
1929
        mod_list.append(node)
1930
        node.master_candidate = True
1931
        node.serial_no += 1
1932
        mc_now += 1
1933
      if mc_now != mc_max:
1934
        # this should not happen
1935
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1936
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1937
      if mod_list:
1938
        self._config_data.cluster.serial_no += 1
1939
        self._WriteConfig()
1940

    
1941
    return mod_list
1942

    
1943
  def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1944
    """Add a given node to the specified group.
1945

1946
    """
1947
    if nodegroup_uuid not in self._config_data.nodegroups:
1948
      # This can happen if a node group gets deleted between its lookup and
1949
      # when we're adding the first node to it, since we don't keep a lock in
1950
      # the meantime. It's ok though, as we'll fail cleanly if the node group
1951
      # is not found anymore.
1952
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1953
    if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1954
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1955

    
1956
  def _UnlockedRemoveNodeFromGroup(self, node):
1957
    """Remove a given node from its group.
1958

1959
    """
1960
    nodegroup = node.group
1961
    if nodegroup not in self._config_data.nodegroups:
1962
      logging.warning("Warning: node '%s' has unknown node group '%s'"
1963
                      " (while being removed from it)", node.name, nodegroup)
1964
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
1965
    if node.name not in nodegroup_obj.members:
1966
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
1967
                      " (while being removed from it)", node.name, nodegroup)
1968
    else:
1969
      nodegroup_obj.members.remove(node.name)
1970

    
1971
  @locking.ssynchronized(_config_lock)
1972
  def AssignGroupNodes(self, mods):
1973
    """Changes the group of a number of nodes.
1974

1975
    @type mods: list of tuples; (node name, new group UUID)
1976
    @param mods: Node membership modifications
1977

1978
    """
1979
    groups = self._config_data.nodegroups
1980
    nodes = self._config_data.nodes
1981

    
1982
    resmod = []
1983

    
1984
    # Try to resolve names/UUIDs first
1985
    for (node_name, new_group_uuid) in mods:
1986
      try:
1987
        node = nodes[node_name]
1988
      except KeyError:
1989
        raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
1990

    
1991
      if node.group == new_group_uuid:
1992
        # Node is being assigned to its current group
1993
        logging.debug("Node '%s' was assigned to its current group (%s)",
1994
                      node_name, node.group)
1995
        continue
1996

    
1997
      # Try to find current group of node
1998
      try:
1999
        old_group = groups[node.group]
2000
      except KeyError:
2001
        raise errors.ConfigurationError("Unable to find old group '%s'" %
2002
                                        node.group)
2003

    
2004
      # Try to find new group for node
2005
      try:
2006
        new_group = groups[new_group_uuid]
2007
      except KeyError:
2008
        raise errors.ConfigurationError("Unable to find new group '%s'" %
2009
                                        new_group_uuid)
2010

    
2011
      assert node.name in old_group.members, \
2012
        ("Inconsistent configuration: node '%s' not listed in members for its"
2013
         " old group '%s'" % (node.name, old_group.uuid))
2014
      assert node.name not in new_group.members, \
2015
        ("Inconsistent configuration: node '%s' already listed in members for"
2016
         " its new group '%s'" % (node.name, new_group.uuid))
2017

    
2018
      resmod.append((node, old_group, new_group))
2019

    
2020
    # Apply changes
2021
    for (node, old_group, new_group) in resmod:
2022
      assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2023
        "Assigning to current group is not possible"
2024

    
2025
      node.group = new_group.uuid
2026

    
2027
      # Update members of involved groups
2028
      if node.name in old_group.members:
2029
        old_group.members.remove(node.name)
2030
      if node.name not in new_group.members:
2031
        new_group.members.append(node.name)
2032

    
2033
    # Update timestamps and serials (only once per node/group object)
2034
    now = time.time()
2035
    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2036
      obj.serial_no += 1
2037
      obj.mtime = now
2038

    
2039
    # Force ssconf update
2040
    self._config_data.cluster.serial_no += 1
2041

    
2042
    self._WriteConfig()
2043

    
2044
  def _BumpSerialNo(self):
2045
    """Bump up the serial number of the config.
2046

2047
    """
2048
    self._config_data.serial_no += 1
2049
    self._config_data.mtime = time.time()
2050

    
2051
  def _AllUUIDObjects(self):
2052
    """Returns all objects with uuid attributes.
2053

2054
    """
2055
    return (self._config_data.instances.values() +
2056
            self._config_data.nodes.values() +
2057
            self._config_data.nodegroups.values() +
2058
            self._config_data.networks.values() +
2059
            self._AllDisks() +
2060
            self._AllNICs() +
2061
            [self._config_data.cluster])
2062

    
2063
  def _OpenConfig(self, accept_foreign):
2064
    """Read the config data from disk.
2065

2066
    """
2067
    raw_data = utils.ReadFile(self._cfg_file)
2068

    
2069
    try:
2070
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2071
    except Exception, err:
2072
      raise errors.ConfigurationError(err)
2073

    
2074
    # Make sure the configuration has the right version
2075
    _ValidateConfig(data)
2076

    
2077
    if (not hasattr(data, "cluster") or
2078
        not hasattr(data.cluster, "rsahostkeypub")):
2079
      raise errors.ConfigurationError("Incomplete configuration"
2080
                                      " (missing cluster.rsahostkeypub)")
2081

    
2082
    if data.cluster.master_node != self._my_hostname and not accept_foreign:
2083
      msg = ("The configuration denotes node %s as master, while my"
2084
             " hostname is %s; opening a foreign configuration is only"
2085
             " possible in accept_foreign mode" %
2086
             (data.cluster.master_node, self._my_hostname))
2087
      raise errors.ConfigurationError(msg)
2088

    
2089
    self._config_data = data
2090
    # reset the last serial as -1 so that the next write will cause
2091
    # ssconf update
2092
    self._last_cluster_serial = -1
2093

    
2094
    # Upgrade configuration if needed
2095
    self._UpgradeConfig()
2096

    
2097
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
2098

    
2099
  def _UpgradeConfig(self):
2100
    """Run any upgrade steps.
2101

2102
    This method performs both in-object upgrades and also update some data
2103
    elements that need uniqueness across the whole configuration or interact
2104
    with other objects.
2105

2106
    @warning: this function will call L{_WriteConfig()}, but also
2107
        L{DropECReservations} so it needs to be called only from a
2108
        "safe" place (the constructor). If one wanted to call it with
2109
        the lock held, a DropECReservationUnlocked would need to be
2110
        created first, to avoid causing deadlock.
2111

2112
    """
2113
    # Keep a copy of the persistent part of _config_data to check for changes
2114
    # Serialization doesn't guarantee order in dictionaries
2115
    oldconf = copy.deepcopy(self._config_data.ToDict())
2116

    
2117
    # In-object upgrades
2118
    self._config_data.UpgradeConfig()
2119

    
2120
    for item in self._AllUUIDObjects():
2121
      if item.uuid is None:
2122
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2123
    if not self._config_data.nodegroups:
2124
      default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2125
      default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2126
                                            members=[])
2127
      self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2128
    for node in self._config_data.nodes.values():
2129
      if not node.group:
2130
        node.group = self.LookupNodeGroup(None)
2131
      # This is technically *not* an upgrade, but needs to be done both when
2132
      # nodegroups are being added, and upon normally loading the config,
2133
      # because the members list of a node group is discarded upon
2134
      # serializing/deserializing the object.
2135
      self._UnlockedAddNodeToGroup(node.name, node.group)
2136

    
2137
    modified = (oldconf != self._config_data.ToDict())
2138
    if modified:
2139
      self._WriteConfig()
2140
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2141
      # only called at config init time, without the lock held
2142
      self.DropECReservations(_UPGRADE_CONFIG_JID)
2143
    else:
2144
      config_errors = self._UnlockedVerifyConfig()
2145
      if config_errors:
2146
        errmsg = ("Loaded configuration data is not consistent: %s" %
2147
                  (utils.CommaJoin(config_errors)))
2148
        logging.critical(errmsg)
2149

    
2150
  def _DistributeConfig(self, feedback_fn):
2151
    """Distribute the configuration to the other nodes.
2152

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

2156
    """
2157
    if self._offline:
2158
      return True
2159

    
2160
    bad = False
2161

    
2162
    node_list = []
2163
    addr_list = []
2164
    myhostname = self._my_hostname
2165
    # we can skip checking whether _UnlockedGetNodeInfo returns None
2166
    # since the node list comes from _UnlocketGetNodeList, and we are
2167
    # called with the lock held, so no modifications should take place
2168
    # in between
2169
    for node_name in self._UnlockedGetNodeList():
2170
      if node_name == myhostname:
2171
        continue
2172
      node_info = self._UnlockedGetNodeInfo(node_name)
2173
      if not node_info.master_candidate:
2174
        continue
2175
      node_list.append(node_info.name)
2176
      addr_list.append(node_info.primary_ip)
2177

    
2178
    # TODO: Use dedicated resolver talking to config writer for name resolution
2179
    result = \
2180
      self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2181
    for to_node, to_result in result.items():
2182
      msg = to_result.fail_msg
2183
      if msg:
2184
        msg = ("Copy of file %s to node %s failed: %s" %
2185
               (self._cfg_file, to_node, msg))
2186
        logging.error(msg)
2187

    
2188
        if feedback_fn:
2189
          feedback_fn(msg)
2190

    
2191
        bad = True
2192

    
2193
    return not bad
2194

    
2195
  def _WriteConfig(self, destination=None, feedback_fn=None):
2196
    """Write the configuration data to persistent storage.
2197

2198
    """
2199
    assert feedback_fn is None or callable(feedback_fn)
2200

    
2201
    # Warn on config errors, but don't abort the save - the
2202
    # configuration has already been modified, and we can't revert;
2203
    # the best we can do is to warn the user and save as is, leaving
2204
    # recovery to the user
2205
    config_errors = self._UnlockedVerifyConfig()
2206
    if config_errors:
2207
      errmsg = ("Configuration data is not consistent: %s" %
2208
                (utils.CommaJoin(config_errors)))
2209
      logging.critical(errmsg)
2210
      if feedback_fn:
2211
        feedback_fn(errmsg)
2212

    
2213
    if destination is None:
2214
      destination = self._cfg_file
2215
    self._BumpSerialNo()
2216
    txt = serializer.Dump(self._config_data.ToDict())
2217

    
2218
    getents = self._getents()
2219
    try:
2220
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2221
                               close=False, gid=getents.confd_gid, mode=0640)
2222
    except errors.LockError:
2223
      raise errors.ConfigurationError("The configuration file has been"
2224
                                      " modified since the last write, cannot"
2225
                                      " update")
2226
    try:
2227
      self._cfg_id = utils.GetFileID(fd=fd)
2228
    finally:
2229
      os.close(fd)
2230

    
2231
    self.write_count += 1
2232

    
2233
    # and redistribute the config file to master candidates
2234
    self._DistributeConfig(feedback_fn)
2235

    
2236
    # Write ssconf files on all nodes (including locally)
2237
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
2238
      if not self._offline:
2239
        result = self._GetRpc(None).call_write_ssconf_files(
2240
          self._UnlockedGetOnlineNodeList(),
2241
          self._UnlockedGetSsconfValues())
2242

    
2243
        for nname, nresu in result.items():
2244
          msg = nresu.fail_msg
2245
          if msg:
2246
            errmsg = ("Error while uploading ssconf files to"
2247
                      " node %s: %s" % (nname, msg))
2248
            logging.warning(errmsg)
2249

    
2250
            if feedback_fn:
2251
              feedback_fn(errmsg)
2252

    
2253
      self._last_cluster_serial = self._config_data.cluster.serial_no
2254

    
2255
  def _UnlockedGetSsconfValues(self):
2256
    """Return the values needed by ssconf.
2257

2258
    @rtype: dict
2259
    @return: a dictionary with keys the ssconf names and values their
2260
        associated value
2261

2262
    """
2263
    fn = "\n".join
2264
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2265
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
2266
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
2267
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2268
                    for ninfo in node_info]
2269
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2270
                    for ninfo in node_info]
2271

    
2272
    instance_data = fn(instance_names)
2273
    off_data = fn(node.name for node in node_info if node.offline)
2274
    on_data = fn(node.name for node in node_info if not node.offline)
2275
    mc_data = fn(node.name for node in node_info if node.master_candidate)
2276
    mc_ips_data = fn(node.primary_ip for node in node_info
2277
                     if node.master_candidate)
2278
    node_data = fn(node_names)
2279
    node_pri_ips_data = fn(node_pri_ips)
2280
    node_snd_ips_data = fn(node_snd_ips)
2281

    
2282
    cluster = self._config_data.cluster
2283
    cluster_tags = fn(cluster.GetTags())
2284

    
2285
    hypervisor_list = fn(cluster.enabled_hypervisors)
2286

    
2287
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2288

    
2289
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2290
                  self._config_data.nodegroups.values()]
2291
    nodegroups_data = fn(utils.NiceSort(nodegroups))
2292
    networks = ["%s %s" % (net.uuid, net.name) for net in
2293
                self._config_data.networks.values()]
2294
    networks_data = fn(utils.NiceSort(networks))
2295

    
2296
    ssconf_values = {
2297
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
2298
      constants.SS_CLUSTER_TAGS: cluster_tags,
2299
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2300
      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2301
      constants.SS_MASTER_CANDIDATES: mc_data,
2302
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2303
      constants.SS_MASTER_IP: cluster.master_ip,
2304
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
2305
      constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2306
      constants.SS_MASTER_NODE: cluster.master_node,
2307
      constants.SS_NODE_LIST: node_data,
2308
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2309
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2310
      constants.SS_OFFLINE_NODES: off_data,
2311
      constants.SS_ONLINE_NODES: on_data,
2312
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2313
      constants.SS_INSTANCE_LIST: instance_data,
2314
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2315
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
2316
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2317
      constants.SS_UID_POOL: uid_pool,
2318
      constants.SS_NODEGROUPS: nodegroups_data,
2319
      constants.SS_NETWORKS: networks_data,
2320
      }
2321
    bad_values = [(k, v) for k, v in ssconf_values.items()
2322
                  if not isinstance(v, (str, basestring))]
2323
    if bad_values:
2324
      err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2325
      raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2326
                                      " values: %s" % err)
2327
    return ssconf_values
2328

    
2329
  @locking.ssynchronized(_config_lock, shared=1)
2330
  def GetSsconfValues(self):
2331
    """Wrapper using lock around _UnlockedGetSsconf().
2332

2333
    """
2334
    return self._UnlockedGetSsconfValues()
2335

    
2336
  @locking.ssynchronized(_config_lock, shared=1)
2337
  def GetVGName(self):
2338
    """Return the volume group name.
2339

2340
    """
2341
    return self._config_data.cluster.volume_group_name
2342

    
2343
  @locking.ssynchronized(_config_lock)
2344
  def SetVGName(self, vg_name):
2345
    """Set the volume group name.
2346

2347
    """
2348
    self._config_data.cluster.volume_group_name = vg_name
2349
    self._config_data.cluster.serial_no += 1
2350
    self._WriteConfig()
2351

    
2352
  @locking.ssynchronized(_config_lock, shared=1)
2353
  def GetDRBDHelper(self):
2354
    """Return DRBD usermode helper.
2355

2356
    """
2357
    return self._config_data.cluster.drbd_usermode_helper
2358

    
2359
  @locking.ssynchronized(_config_lock)
2360
  def SetDRBDHelper(self, drbd_helper):
2361
    """Set DRBD usermode helper.
2362

2363
    """
2364
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
2365
    self._config_data.cluster.serial_no += 1
2366
    self._WriteConfig()
2367

    
2368
  @locking.ssynchronized(_config_lock, shared=1)
2369
  def GetMACPrefix(self):
2370
    """Return the mac prefix.
2371

2372
    """
2373
    return self._config_data.cluster.mac_prefix
2374

    
2375
  @locking.ssynchronized(_config_lock, shared=1)
2376
  def GetClusterInfo(self):
2377
    """Returns information about the cluster
2378

2379
    @rtype: L{objects.Cluster}
2380
    @return: the cluster object
2381

2382
    """
2383
    return self._config_data.cluster
2384

    
2385
  @locking.ssynchronized(_config_lock, shared=1)
2386
  def HasAnyDiskOfType(self, dev_type):
2387
    """Check if in there is at disk of the given type in the configuration.
2388

2389
    """
2390
    return self._config_data.HasAnyDiskOfType(dev_type)
2391

    
2392
  @locking.ssynchronized(_config_lock)
2393
  def Update(self, target, feedback_fn, ec_id=None):
2394
    """Notify function to be called after updates.
2395

2396
    This function must be called when an object (as returned by
2397
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2398
    caller wants the modifications saved to the backing store. Note
2399
    that all modified objects will be saved, but the target argument
2400
    is the one the caller wants to ensure that it's saved.
2401

2402
    @param target: an instance of either L{objects.Cluster},
2403
        L{objects.Node} or L{objects.Instance} which is existing in
2404
        the cluster
2405
    @param feedback_fn: Callable feedback function
2406

2407
    """
2408
    if self._config_data is None:
2409
      raise errors.ProgrammerError("Configuration file not read,"
2410
                                   " cannot save.")
2411
    update_serial = False
2412
    if isinstance(target, objects.Cluster):
2413
      test = target == self._config_data.cluster
2414
    elif isinstance(target, objects.Node):
2415
      test = target in self._config_data.nodes.values()
2416
      update_serial = True
2417
    elif isinstance(target, objects.Instance):
2418
      test = target in self._config_data.instances.values()
2419
    elif isinstance(target, objects.NodeGroup):
2420
      test = target in self._config_data.nodegroups.values()
2421
    elif isinstance(target, objects.Network):
2422
      test = target in self._config_data.networks.values()
2423
    else:
2424
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
2425
                                   " ConfigWriter.Update" % type(target))
2426
    if not test:
2427
      raise errors.ConfigurationError("Configuration updated since object"
2428
                                      " has been read or unknown object")
2429
    target.serial_no += 1
2430
    target.mtime = now = time.time()
2431

    
2432
    if update_serial:
2433
      # for node updates, we need to increase the cluster serial too
2434
      self._config_data.cluster.serial_no += 1
2435
      self._config_data.cluster.mtime = now
2436

    
2437
    if isinstance(target, objects.Instance):
2438
      self._UnlockedReleaseDRBDMinors(target.name)
2439

    
2440
    if ec_id is not None:
2441
      # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2442
      self._UnlockedCommitTemporaryIps(ec_id)
2443

    
2444
    self._WriteConfig(feedback_fn=feedback_fn)
2445

    
2446
  @locking.ssynchronized(_config_lock)
2447
  def DropECReservations(self, ec_id):
2448
    """Drop per-execution-context reservations
2449

2450
    """
2451
    for rm in self._all_rms:
2452
      rm.DropECReservations(ec_id)
2453

    
2454
  @locking.ssynchronized(_config_lock, shared=1)
2455
  def GetAllNetworksInfo(self):
2456
    """Get configuration info of all the networks.
2457

2458
    """
2459
    return dict(self._config_data.networks)
2460

    
2461
  def _UnlockedGetNetworkList(self):
2462
    """Get the list of networks.
2463

2464
    This function is for internal use, when the config lock is already held.
2465

2466
    """
2467
    return self._config_data.networks.keys()
2468

    
2469
  @locking.ssynchronized(_config_lock, shared=1)
2470
  def GetNetworkList(self):
2471
    """Get the list of networks.
2472

2473
    @return: array of networks, ex. ["main", "vlan100", "200]
2474

2475
    """
2476
    return self._UnlockedGetNetworkList()
2477

    
2478
  @locking.ssynchronized(_config_lock, shared=1)
2479
  def GetNetworkNames(self):
2480
    """Get a list of network names
2481

2482
    """
2483
    names = [net.name
2484
             for net in self._config_data.networks.values()]
2485
    return names
2486

    
2487
  def _UnlockedGetNetwork(self, uuid):
2488
    """Returns information about a network.
2489

2490
    This function is for internal use, when the config lock is already held.
2491

2492
    """
2493
    if uuid not in self._config_data.networks:
2494
      return None
2495

    
2496
    return self._config_data.networks[uuid]
2497

    
2498
  @locking.ssynchronized(_config_lock, shared=1)
2499
  def GetNetwork(self, uuid):
2500
    """Returns information about a network.
2501

2502
    It takes the information from the configuration file.
2503

2504
    @param uuid: UUID of the network
2505

2506
    @rtype: L{objects.Network}
2507
    @return: the network object
2508

2509
    """
2510
    return self._UnlockedGetNetwork(uuid)
2511

    
2512
  @locking.ssynchronized(_config_lock)
2513
  def AddNetwork(self, net, ec_id, check_uuid=True):
2514
    """Add a network to the configuration.
2515

2516
    @type net: L{objects.Network}
2517
    @param net: the Network object to add
2518
    @type ec_id: string
2519
    @param ec_id: unique id for the job to use when creating a missing UUID
2520

2521
    """
2522
    self._UnlockedAddNetwork(net, ec_id, check_uuid)
2523
    self._WriteConfig()
2524

    
2525
  def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2526
    """Add a network to the configuration.
2527

2528
    """
2529
    logging.info("Adding network %s to configuration", net.name)
2530

    
2531
    if check_uuid:
2532
      self._EnsureUUID(net, ec_id)
2533

    
2534
    net.serial_no = 1
2535
    self._config_data.networks[net.uuid] = net
2536
    self._config_data.cluster.serial_no += 1
2537

    
2538
  def _UnlockedLookupNetwork(self, target):
2539
    """Lookup a network's UUID.
2540

2541
    @type target: string
2542
    @param target: network name or UUID
2543
    @rtype: string
2544
    @return: network UUID
2545
    @raises errors.OpPrereqError: when the target network cannot be found
2546

2547
    """
2548
    if target is None:
2549
      return None
2550
    if target in self._config_data.networks:
2551
      return target
2552
    for net in self._config_data.networks.values():
2553
      if net.name == target:
2554
        return net.uuid
2555
    raise errors.OpPrereqError("Network '%s' not found" % target,
2556
                               errors.ECODE_NOENT)
2557

    
2558
  @locking.ssynchronized(_config_lock, shared=1)
2559
  def LookupNetwork(self, target):
2560
    """Lookup a network's UUID.
2561

2562
    This function is just a wrapper over L{_UnlockedLookupNetwork}.
2563

2564
    @type target: string
2565
    @param target: network name or UUID
2566
    @rtype: string
2567
    @return: network UUID
2568

2569
    """
2570
    return self._UnlockedLookupNetwork(target)
2571

    
2572
  @locking.ssynchronized(_config_lock)
2573
  def RemoveNetwork(self, network_uuid):
2574
    """Remove a network from the configuration.
2575

2576
    @type network_uuid: string
2577
    @param network_uuid: the UUID of the network to remove
2578

2579
    """
2580
    logging.info("Removing network %s from configuration", network_uuid)
2581

    
2582
    if network_uuid not in self._config_data.networks:
2583
      raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2584

    
2585
    del self._config_data.networks[network_uuid]
2586
    self._config_data.cluster.serial_no += 1
2587
    self._WriteConfig()
2588

    
2589
  def _UnlockedGetGroupNetParams(self, net_uuid, node):
2590
    """Get the netparams (mode, link) of a network.
2591

2592
    Get a network's netparams for a given node.
2593

2594
    @type net_uuid: string
2595
    @param net_uuid: network uuid
2596
    @type node: string
2597
    @param node: node name
2598
    @rtype: dict or None
2599
    @return: netparams
2600

2601
    """
2602
    node_info = self._UnlockedGetNodeInfo(node)
2603
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2604
    netparams = nodegroup_info.networks.get(net_uuid, None)
2605

    
2606
    return netparams
2607

    
2608
  @locking.ssynchronized(_config_lock, shared=1)
2609
  def GetGroupNetParams(self, net_uuid, node):
2610
    """Locking wrapper of _UnlockedGetGroupNetParams()
2611

2612
    """
2613
    return self._UnlockedGetGroupNetParams(net_uuid, node)
2614

    
2615
  @locking.ssynchronized(_config_lock, shared=1)
2616
  def CheckIPInNodeGroup(self, ip, node):
2617
    """Check IP uniqueness in nodegroup.
2618

2619
    Check networks that are connected in the node's node group
2620
    if ip is contained in any of them. Used when creating/adding
2621
    a NIC to ensure uniqueness among nodegroups.
2622

2623
    @type ip: string
2624
    @param ip: ip address
2625
    @type node: string
2626
    @param node: node name
2627
    @rtype: (string, dict) or (None, None)
2628
    @return: (network name, netparams)
2629

2630
    """
2631
    if ip is None:
2632
      return (None, None)
2633
    node_info = self._UnlockedGetNodeInfo(node)
2634
    nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2635
    for net_uuid in nodegroup_info.networks.keys():
2636
      net_info = self._UnlockedGetNetwork(net_uuid)
2637
      pool = network.AddressPool(net_info)
2638
      if pool.Contains(ip):
2639
        return (net_info.name, nodegroup_info.networks[net_uuid])
2640

    
2641
    return (None, None)