Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 54c17091

History | View | Annotate | Download (72.5 kB)

1
#
2
#
3

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

    
21

    
22
"""Transportable objects for Ganeti.
23

24
This module provides small, mostly data-only objects which are safe to
25
pass to and from external parties.
26

27
"""
28

    
29
# pylint: disable=E0203,W0201,R0902
30

    
31
# E0203: Access to member %r before its definition, since we use
32
# objects.py which doesn't explicitly initialise its members
33

    
34
# W0201: Attribute '%s' defined outside __init__
35

    
36
# R0902: Allow instances of these objects to have more than 20 attributes
37

    
38
import ConfigParser
39
import re
40
import copy
41
import logging
42
import time
43
from cStringIO import StringIO
44

    
45
from ganeti import errors
46
from ganeti import constants
47
from ganeti import netutils
48
from ganeti import outils
49
from ganeti import utils
50
from ganeti import serializer
51

    
52
from socket import AF_INET
53

    
54

    
55
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
56
           "OS", "Node", "NodeGroup", "Cluster", "FillDict", "Network"]
57

    
58
_TIMESTAMPS = ["ctime", "mtime"]
59
_UUID = ["uuid"]
60

    
61

    
62
def FillDict(defaults_dict, custom_dict, skip_keys=None):
63
  """Basic function to apply settings on top a default dict.
64

65
  @type defaults_dict: dict
66
  @param defaults_dict: dictionary holding the default values
67
  @type custom_dict: dict
68
  @param custom_dict: dictionary holding customized value
69
  @type skip_keys: list
70
  @param skip_keys: which keys not to fill
71
  @rtype: dict
72
  @return: dict with the 'full' values
73

74
  """
75
  ret_dict = copy.deepcopy(defaults_dict)
76
  ret_dict.update(custom_dict)
77
  if skip_keys:
78
    for k in skip_keys:
79
      if k in ret_dict:
80
        del ret_dict[k]
81
  return ret_dict
82

    
83

    
84
def FillIPolicy(default_ipolicy, custom_ipolicy):
85
  """Fills an instance policy with defaults.
86

87
  """
88
  assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS
89
  ret_dict = copy.deepcopy(custom_ipolicy)
90
  for key in default_ipolicy:
91
    if key not in ret_dict:
92
      ret_dict[key] = copy.deepcopy(default_ipolicy[key])
93
    elif key == constants.ISPECS_STD:
94
      ret_dict[key] = FillDict(default_ipolicy[key], ret_dict[key])
95
  return ret_dict
96

    
97

    
98
def FillDiskParams(default_dparams, custom_dparams, skip_keys=None):
99
  """Fills the disk parameter defaults.
100

101
  @see: L{FillDict} for parameters and return value
102

103
  """
104
  assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES
105

    
106
  return dict((dt, FillDict(default_dparams[dt], custom_dparams.get(dt, {}),
107
                             skip_keys=skip_keys))
108
              for dt in constants.DISK_TEMPLATES)
109

    
110

    
111
def UpgradeGroupedParams(target, defaults):
112
  """Update all groups for the target parameter.
113

114
  @type target: dict of dicts
115
  @param target: {group: {parameter: value}}
116
  @type defaults: dict
117
  @param defaults: default parameter values
118

119
  """
120
  if target is None:
121
    target = {constants.PP_DEFAULT: defaults}
122
  else:
123
    for group in target:
124
      target[group] = FillDict(defaults, target[group])
125
  return target
126

    
127

    
128
def UpgradeBeParams(target):
129
  """Update the be parameters dict to the new format.
130

131
  @type target: dict
132
  @param target: "be" parameters dict
133

134
  """
135
  if constants.BE_MEMORY in target:
136
    memory = target[constants.BE_MEMORY]
137
    target[constants.BE_MAXMEM] = memory
138
    target[constants.BE_MINMEM] = memory
139
    del target[constants.BE_MEMORY]
140

    
141

    
142
def UpgradeDiskParams(diskparams):
143
  """Upgrade the disk parameters.
144

145
  @type diskparams: dict
146
  @param diskparams: disk parameters to upgrade
147
  @rtype: dict
148
  @return: the upgraded disk parameters dict
149

150
  """
151
  if not diskparams:
152
    result = {}
153
  else:
154
    result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams)
155

    
156
  return result
157

    
158

    
159
def UpgradeNDParams(ndparams):
160
  """Upgrade ndparams structure.
161

162
  @type ndparams: dict
163
  @param ndparams: disk parameters to upgrade
164
  @rtype: dict
165
  @return: the upgraded node parameters dict
166

167
  """
168
  if ndparams is None:
169
    ndparams = {}
170

    
171
  if (constants.ND_OOB_PROGRAM in ndparams and
172
      ndparams[constants.ND_OOB_PROGRAM] is None):
173
    # will be reset by the line below
174
    del ndparams[constants.ND_OOB_PROGRAM]
175
  return FillDict(constants.NDC_DEFAULTS, ndparams)
176

    
177

    
178
def MakeEmptyIPolicy():
179
  """Create empty IPolicy dictionary.
180

181
  """
182
  return {}
183

    
184

    
185
class ConfigObject(outils.ValidatedSlots):
186
  """A generic config object.
187

188
  It has the following properties:
189

190
    - provides somewhat safe recursive unpickling and pickling for its classes
191
    - unset attributes which are defined in slots are always returned
192
      as None instead of raising an error
193

194
  Classes derived from this must always declare __slots__ (we use many
195
  config objects and the memory reduction is useful)
196

197
  """
198
  __slots__ = []
199

    
200
  def __getattr__(self, name):
201
    if name not in self.GetAllSlots():
202
      raise AttributeError("Invalid object attribute %s.%s" %
203
                           (type(self).__name__, name))
204
    return None
205

    
206
  def __setstate__(self, state):
207
    slots = self.GetAllSlots()
208
    for name in state:
209
      if name in slots:
210
        setattr(self, name, state[name])
211

    
212
  def Validate(self):
213
    """Validates the slots.
214

215
    This method returns L{None} if the validation succeeds, or raises
216
    an exception otherwise.
217

218
    This method must be implemented by the child classes.
219

220
    @rtype: NoneType
221
    @return: L{None}, if the validation succeeds
222

223
    @raise Exception: validation fails
224

225
    """
226

    
227
  def ToDict(self, _with_private=False):
228
    """Convert to a dict holding only standard python types.
229

230
    The generic routine just dumps all of this object's attributes in
231
    a dict. It does not work if the class has children who are
232
    ConfigObjects themselves (e.g. the nics list in an Instance), in
233
    which case the object should subclass the function in order to
234
    make sure all objects returned are only standard python types.
235

236
    Private fields can be included or not with the _with_private switch.
237
    The actual implementation of this switch is left for those subclassses
238
    with private fields to implement.
239

240
    @type _with_private: bool
241
    @param _with_private: if True, the object will leak its private fields in
242
                          the dictionary representation. If False, the values
243
                          will be replaced with None.
244

245
    """
246
    result = {}
247
    for name in self.GetAllSlots():
248
      value = getattr(self, name, None)
249
      if value is not None:
250
        result[name] = value
251
    return result
252

    
253
  __getstate__ = ToDict
254

    
255
  @classmethod
256
  def FromDict(cls, val):
257
    """Create an object from a dictionary.
258

259
    This generic routine takes a dict, instantiates a new instance of
260
    the given class, and sets attributes based on the dict content.
261

262
    As for `ToDict`, this does not work if the class has children
263
    who are ConfigObjects themselves (e.g. the nics list in an
264
    Instance), in which case the object should subclass the function
265
    and alter the objects.
266

267
    """
268
    if not isinstance(val, dict):
269
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
270
                                      " expected dict, got %s" % type(val))
271
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
272
    obj = cls(**val_str) # pylint: disable=W0142
273
    return obj
274

    
275
  def Copy(self):
276
    """Makes a deep copy of the current object and its children.
277

278
    """
279
    dict_form = self.ToDict()
280
    clone_obj = self.__class__.FromDict(dict_form)
281
    return clone_obj
282

    
283
  def __repr__(self):
284
    """Implement __repr__ for ConfigObjects."""
285
    return repr(self.ToDict())
286

    
287
  def __eq__(self, other):
288
    """Implement __eq__ for ConfigObjects."""
289
    return isinstance(other, self.__class__) and self.ToDict() == other.ToDict()
290

    
291
  def UpgradeConfig(self):
292
    """Fill defaults for missing configuration values.
293

294
    This method will be called at configuration load time, and its
295
    implementation will be object dependent.
296

297
    """
298
    pass
299

    
300

    
301
class TaggableObject(ConfigObject):
302
  """An generic class supporting tags.
303

304
  """
305
  __slots__ = ["tags"]
306
  VALID_TAG_RE = re.compile(r"^[\w.+*/:@-]+$")
307

    
308
  @classmethod
309
  def ValidateTag(cls, tag):
310
    """Check if a tag is valid.
311

312
    If the tag is invalid, an errors.TagError will be raised. The
313
    function has no return value.
314

315
    """
316
    if not isinstance(tag, basestring):
317
      raise errors.TagError("Invalid tag type (not a string)")
318
    if len(tag) > constants.MAX_TAG_LEN:
319
      raise errors.TagError("Tag too long (>%d characters)" %
320
                            constants.MAX_TAG_LEN)
321
    if not tag:
322
      raise errors.TagError("Tags cannot be empty")
323
    if not cls.VALID_TAG_RE.match(tag):
324
      raise errors.TagError("Tag contains invalid characters")
325

    
326
  def GetTags(self):
327
    """Return the tags list.
328

329
    """
330
    tags = getattr(self, "tags", None)
331
    if tags is None:
332
      tags = self.tags = set()
333
    return tags
334

    
335
  def AddTag(self, tag):
336
    """Add a new tag.
337

338
    """
339
    self.ValidateTag(tag)
340
    tags = self.GetTags()
341
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
342
      raise errors.TagError("Too many tags")
343
    self.GetTags().add(tag)
344

    
345
  def RemoveTag(self, tag):
346
    """Remove a tag.
347

348
    """
349
    self.ValidateTag(tag)
350
    tags = self.GetTags()
351
    try:
352
      tags.remove(tag)
353
    except KeyError:
354
      raise errors.TagError("Tag not found")
355

    
356
  def ToDict(self, _with_private=False):
357
    """Taggable-object-specific conversion to standard python types.
358

359
    This replaces the tags set with a list.
360

361
    """
362
    bo = super(TaggableObject, self).ToDict(_with_private=_with_private)
363

    
364
    tags = bo.get("tags", None)
365
    if isinstance(tags, set):
366
      bo["tags"] = list(tags)
367
    return bo
368

    
369
  @classmethod
370
  def FromDict(cls, val):
371
    """Custom function for instances.
372

373
    """
374
    obj = super(TaggableObject, cls).FromDict(val)
375
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
376
      obj.tags = set(obj.tags)
377
    return obj
378

    
379

    
380
class MasterNetworkParameters(ConfigObject):
381
  """Network configuration parameters for the master
382

383
  @ivar uuid: master nodes UUID
384
  @ivar ip: master IP
385
  @ivar netmask: master netmask
386
  @ivar netdev: master network device
387
  @ivar ip_family: master IP family
388

389
  """
390
  __slots__ = [
391
    "uuid",
392
    "ip",
393
    "netmask",
394
    "netdev",
395
    "ip_family",
396
    ]
397

    
398

    
399
class ConfigData(ConfigObject):
400
  """Top-level config object."""
401
  __slots__ = [
402
    "version",
403
    "cluster",
404
    "nodes",
405
    "nodegroups",
406
    "instances",
407
    "networks",
408
    "disks",
409
    "serial_no",
410
    ] + _TIMESTAMPS
411

    
412
  def ToDict(self, _with_private=False):
413
    """Custom function for top-level config data.
414

415
    This just replaces the list of nodes, instances, nodegroups,
416
    networks, disks and the cluster with standard python types.
417

418
    """
419
    mydict = super(ConfigData, self).ToDict(_with_private=_with_private)
420
    mydict["cluster"] = mydict["cluster"].ToDict()
421
    for key in "nodes", "instances", "nodegroups", "networks", "disks":
422
      mydict[key] = outils.ContainerToDicts(mydict[key])
423

    
424
    return mydict
425

    
426
  @classmethod
427
  def FromDict(cls, val):
428
    """Custom function for top-level config data
429

430
    """
431
    obj = super(ConfigData, cls).FromDict(val)
432
    obj.cluster = Cluster.FromDict(obj.cluster)
433
    obj.nodes = outils.ContainerFromDicts(obj.nodes, dict, Node)
434
    obj.instances = \
435
      outils.ContainerFromDicts(obj.instances, dict, Instance)
436
    obj.nodegroups = \
437
      outils.ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
438
    obj.networks = outils.ContainerFromDicts(obj.networks, dict, Network)
439
    obj.disks = outils.ContainerFromDicts(obj.disks, dict, Disk)
440
    return obj
441

    
442
  def HasAnyDiskOfType(self, dev_type):
443
    """Check if in there is at disk of the given type in the configuration.
444

445
    @type dev_type: L{constants.DTS_BLOCK}
446
    @param dev_type: the type to look for
447
    @rtype: boolean
448
    @return: boolean indicating if a disk of the given type was found or not
449

450
    """
451
    for instance in self.instances.values():
452
      for disk in instance.disks:
453
        if disk.IsBasedOnDiskType(dev_type):
454
          return True
455
    return False
456

    
457
  def UpgradeConfig(self):
458
    """Fill defaults for missing configuration values.
459

460
    """
461
    self.cluster.UpgradeConfig()
462
    for node in self.nodes.values():
463
      node.UpgradeConfig()
464
    for instance in self.instances.values():
465
      instance.UpgradeConfig()
466
    self._UpgradeEnabledDiskTemplates()
467
    if self.nodegroups is None:
468
      self.nodegroups = {}
469
    for nodegroup in self.nodegroups.values():
470
      nodegroup.UpgradeConfig()
471
      InstancePolicy.UpgradeDiskTemplates(
472
        nodegroup.ipolicy, self.cluster.enabled_disk_templates)
473
    if self.cluster.drbd_usermode_helper is None:
474
      if self.cluster.IsDiskTemplateEnabled(constants.DT_DRBD8):
475
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
476
    if self.networks is None:
477
      self.networks = {}
478
    for network in self.networks.values():
479
      network.UpgradeConfig()
480
    for disk in self.disks.values():
481
      disk.UpgradeConfig()
482

    
483
  def _UpgradeEnabledDiskTemplates(self):
484
    """Upgrade the cluster's enabled disk templates by inspecting the currently
485
       enabled and/or used disk templates.
486

487
    """
488
    if not self.cluster.enabled_disk_templates:
489
      template_set = \
490
        set([inst.disk_template for inst in self.instances.values()])
491
      # Add drbd and plain, if lvm is enabled (by specifying a volume group)
492
      if self.cluster.volume_group_name:
493
        template_set.add(constants.DT_DRBD8)
494
        template_set.add(constants.DT_PLAIN)
495
      # Set enabled_disk_templates to the inferred disk templates. Order them
496
      # according to a preference list that is based on Ganeti's history of
497
      # supported disk templates.
498
      self.cluster.enabled_disk_templates = []
499
      for preferred_template in constants.DISK_TEMPLATE_PREFERENCE:
500
        if preferred_template in template_set:
501
          self.cluster.enabled_disk_templates.append(preferred_template)
502
          template_set.remove(preferred_template)
503
      self.cluster.enabled_disk_templates.extend(list(template_set))
504
    InstancePolicy.UpgradeDiskTemplates(
505
      self.cluster.ipolicy, self.cluster.enabled_disk_templates)
506

    
507

    
508
class NIC(ConfigObject):
509
  """Config object representing a network card."""
510
  __slots__ = ["name", "mac", "ip", "network",
511
               "nicparams", "netinfo", "pci"] + _UUID
512

    
513
  @classmethod
514
  def CheckParameterSyntax(cls, nicparams):
515
    """Check the given parameters for validity.
516

517
    @type nicparams:  dict
518
    @param nicparams: dictionary with parameter names/value
519
    @raise errors.ConfigurationError: when a parameter is not valid
520

521
    """
522
    mode = nicparams[constants.NIC_MODE]
523
    if (mode not in constants.NIC_VALID_MODES and
524
        mode != constants.VALUE_AUTO):
525
      raise errors.ConfigurationError("Invalid NIC mode '%s'" % mode)
526

    
527
    if (mode == constants.NIC_MODE_BRIDGED and
528
        not nicparams[constants.NIC_LINK]):
529
      raise errors.ConfigurationError("Missing bridged NIC link")
530

    
531

    
532
class Disk(ConfigObject):
533
  """Config object representing a block device."""
534
  __slots__ = (["name", "dev_type", "logical_id", "children", "iv_name",
535
                "size", "mode", "params", "spindles", "pci"] + _UUID +
536
               # dynamic_params is special. It depends on the node this instance
537
               # is sent to, and should not be persisted.
538
               ["dynamic_params"])
539

    
540
  def _ComputeAllNodes(self):
541
    """Compute the list of all nodes covered by a device and its children."""
542
    nodes = list()
543

    
544
    if self.dev_type in constants.DTS_DRBD:
545
      nodea, nodeb = self.logical_id[:2]
546
      nodes.append(nodea)
547
      nodes.append(nodeb)
548
    if self.children:
549
      for child in self.children:
550
        nodes.extend(child.all_nodes)
551

    
552
    return tuple(set(nodes))
553

    
554
  all_nodes = property(_ComputeAllNodes, None, None,
555
                       "List of names of all the nodes of a disk")
556

    
557
  def CreateOnSecondary(self):
558
    """Test if this device needs to be created on a secondary node."""
559
    return self.dev_type in (constants.DT_DRBD8, constants.DT_PLAIN)
560

    
561
  def AssembleOnSecondary(self):
562
    """Test if this device needs to be assembled on a secondary node."""
563
    return self.dev_type in (constants.DT_DRBD8, constants.DT_PLAIN)
564

    
565
  def OpenOnSecondary(self):
566
    """Test if this device needs to be opened on a secondary node."""
567
    return self.dev_type in (constants.DT_PLAIN,)
568

    
569
  def StaticDevPath(self):
570
    """Return the device path if this device type has a static one.
571

572
    Some devices (LVM for example) live always at the same /dev/ path,
573
    irrespective of their status. For such devices, we return this
574
    path, for others we return None.
575

576
    @warning: The path returned is not a normalized pathname; callers
577
        should check that it is a valid path.
578

579
    """
580
    if self.dev_type == constants.DT_PLAIN:
581
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
582
    elif self.dev_type == constants.DT_BLOCK:
583
      return self.logical_id[1]
584
    elif self.dev_type == constants.DT_RBD:
585
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
586
    return None
587

    
588
  def ChildrenNeeded(self):
589
    """Compute the needed number of children for activation.
590

591
    This method will return either -1 (all children) or a positive
592
    number denoting the minimum number of children needed for
593
    activation (only mirrored devices will usually return >=0).
594

595
    Currently, only DRBD8 supports diskless activation (therefore we
596
    return 0), for all other we keep the previous semantics and return
597
    -1.
598

599
    """
600
    if self.dev_type == constants.DT_DRBD8:
601
      return 0
602
    return -1
603

    
604
  def IsBasedOnDiskType(self, dev_type):
605
    """Check if the disk or its children are based on the given type.
606

607
    @type dev_type: L{constants.DTS_BLOCK}
608
    @param dev_type: the type to look for
609
    @rtype: boolean
610
    @return: boolean indicating if a device of the given type was found or not
611

612
    """
613
    if self.children:
614
      for child in self.children:
615
        if child.IsBasedOnDiskType(dev_type):
616
          return True
617
    return self.dev_type == dev_type
618

    
619
  def GetNodes(self, node_uuid):
620
    """This function returns the nodes this device lives on.
621

622
    Given the node on which the parent of the device lives on (or, in
623
    case of a top-level device, the primary node of the devices'
624
    instance), this function will return a list of nodes on which this
625
    devices needs to (or can) be assembled.
626

627
    """
628
    if self.dev_type in [constants.DT_PLAIN, constants.DT_FILE,
629
                         constants.DT_BLOCK, constants.DT_RBD,
630
                         constants.DT_EXT, constants.DT_SHARED_FILE,
631
                         constants.DT_GLUSTER]:
632
      result = [node_uuid]
633
    elif self.dev_type in constants.DTS_DRBD:
634
      result = [self.logical_id[0], self.logical_id[1]]
635
      if node_uuid not in result:
636
        raise errors.ConfigurationError("DRBD device passed unknown node")
637
    else:
638
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
639
    return result
640

    
641
  def ComputeNodeTree(self, parent_node_uuid):
642
    """Compute the node/disk tree for this disk and its children.
643

644
    This method, given the node on which the parent disk lives, will
645
    return the list of all (node UUID, disk) pairs which describe the disk
646
    tree in the most compact way. For example, a drbd/lvm stack
647
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
648
    which represents all the top-level devices on the nodes.
649

650
    """
651
    my_nodes = self.GetNodes(parent_node_uuid)
652
    result = [(node, self) for node in my_nodes]
653
    if not self.children:
654
      # leaf device
655
      return result
656
    for node in my_nodes:
657
      for child in self.children:
658
        child_result = child.ComputeNodeTree(node)
659
        if len(child_result) == 1:
660
          # child (and all its descendants) is simple, doesn't split
661
          # over multiple hosts, so we don't need to describe it, our
662
          # own entry for this node describes it completely
663
          continue
664
        else:
665
          # check if child nodes differ from my nodes; note that
666
          # subdisk can differ from the child itself, and be instead
667
          # one of its descendants
668
          for subnode, subdisk in child_result:
669
            if subnode not in my_nodes:
670
              result.append((subnode, subdisk))
671
            # otherwise child is under our own node, so we ignore this
672
            # entry (but probably the other results in the list will
673
            # be different)
674
    return result
675

    
676
  def ComputeGrowth(self, amount):
677
    """Compute the per-VG growth requirements.
678

679
    This only works for VG-based disks.
680

681
    @type amount: integer
682
    @param amount: the desired increase in (user-visible) disk space
683
    @rtype: dict
684
    @return: a dictionary of volume-groups and the required size
685

686
    """
687
    if self.dev_type == constants.DT_PLAIN:
688
      return {self.logical_id[0]: amount}
689
    elif self.dev_type == constants.DT_DRBD8:
690
      if self.children:
691
        return self.children[0].ComputeGrowth(amount)
692
      else:
693
        return {}
694
    else:
695
      # Other disk types do not require VG space
696
      return {}
697

    
698
  def RecordGrow(self, amount):
699
    """Update the size of this disk after growth.
700

701
    This method recurses over the disks's children and updates their
702
    size correspondigly. The method needs to be kept in sync with the
703
    actual algorithms from bdev.
704

705
    """
706
    if self.dev_type in (constants.DT_PLAIN, constants.DT_FILE,
707
                         constants.DT_RBD, constants.DT_EXT,
708
                         constants.DT_SHARED_FILE, constants.DT_GLUSTER):
709
      self.size += amount
710
    elif self.dev_type == constants.DT_DRBD8:
711
      if self.children:
712
        self.children[0].RecordGrow(amount)
713
      self.size += amount
714
    else:
715
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
716
                                   " disk type %s" % self.dev_type)
717

    
718
  def Update(self, size=None, mode=None, spindles=None):
719
    """Apply changes to size, spindles and mode.
720

721
    """
722
    if self.dev_type == constants.DT_DRBD8:
723
      if self.children:
724
        self.children[0].Update(size=size, mode=mode)
725
    else:
726
      assert not self.children
727

    
728
    if size is not None:
729
      self.size = size
730
    if mode is not None:
731
      self.mode = mode
732
    if spindles is not None:
733
      self.spindles = spindles
734

    
735
  def UnsetSize(self):
736
    """Sets recursively the size to zero for the disk and its children.
737

738
    """
739
    if self.children:
740
      for child in self.children:
741
        child.UnsetSize()
742
    self.size = 0
743

    
744
  def UpdateDynamicDiskParams(self, target_node_uuid, nodes_ip):
745
    """Updates the dynamic disk params for the given node.
746

747
    This is mainly used for drbd, which needs ip/port configuration.
748

749
    Arguments:
750
      - target_node_uuid: the node UUID we wish to configure for
751
      - nodes_ip: a mapping of node name to ip
752

753
    The target_node must exist in nodes_ip, and should be one of the
754
    nodes in the logical ID if this device is a DRBD device.
755

756
    """
757
    if self.children:
758
      for child in self.children:
759
        child.UpdateDynamicDiskParams(target_node_uuid, nodes_ip)
760

    
761
    dyn_disk_params = {}
762
    if self.logical_id is not None and self.dev_type in constants.DTS_DRBD:
763
      pnode_uuid, snode_uuid, _, pminor, sminor, _ = self.logical_id
764
      if target_node_uuid not in (pnode_uuid, snode_uuid):
765
        # disk object is being sent to neither the primary nor the secondary
766
        # node. reset the dynamic parameters, the target node is not
767
        # supposed to use them.
768
        self.dynamic_params = dyn_disk_params
769
        return
770

    
771
      pnode_ip = nodes_ip.get(pnode_uuid, None)
772
      snode_ip = nodes_ip.get(snode_uuid, None)
773
      if pnode_ip is None or snode_ip is None:
774
        raise errors.ConfigurationError("Can't find primary or secondary node"
775
                                        " for %s" % str(self))
776
      if pnode_uuid == target_node_uuid:
777
        dyn_disk_params[constants.DDP_LOCAL_IP] = pnode_ip
778
        dyn_disk_params[constants.DDP_REMOTE_IP] = snode_ip
779
        dyn_disk_params[constants.DDP_LOCAL_MINOR] = pminor
780
        dyn_disk_params[constants.DDP_REMOTE_MINOR] = sminor
781
      else: # it must be secondary, we tested above
782
        dyn_disk_params[constants.DDP_LOCAL_IP] = snode_ip
783
        dyn_disk_params[constants.DDP_REMOTE_IP] = pnode_ip
784
        dyn_disk_params[constants.DDP_LOCAL_MINOR] = sminor
785
        dyn_disk_params[constants.DDP_REMOTE_MINOR] = pminor
786

    
787
    self.dynamic_params = dyn_disk_params
788

    
789
  # pylint: disable=W0221
790
  def ToDict(self, include_dynamic_params=False,
791
             _with_private=False):
792
    """Disk-specific conversion to standard python types.
793

794
    This replaces the children lists of objects with lists of
795
    standard python types.
796

797
    """
798
    bo = super(Disk, self).ToDict()
799
    if not include_dynamic_params and "dynamic_params" in bo:
800
      del bo["dynamic_params"]
801

    
802
    for attr in ("children",):
803
      alist = bo.get(attr, None)
804
      if alist:
805
        bo[attr] = outils.ContainerToDicts(alist)
806
    return bo
807

    
808
  @classmethod
809
  def FromDict(cls, val):
810
    """Custom function for Disks
811

812
    """
813
    obj = super(Disk, cls).FromDict(val)
814
    if obj.children:
815
      obj.children = outils.ContainerFromDicts(obj.children, list, Disk)
816
    if obj.logical_id and isinstance(obj.logical_id, list):
817
      obj.logical_id = tuple(obj.logical_id)
818
    if obj.dev_type in constants.DTS_DRBD:
819
      # we need a tuple of length six here
820
      if len(obj.logical_id) < 6:
821
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
822
    return obj
823

    
824
  def __str__(self):
825
    """Custom str() formatter for disks.
826

827
    """
828
    if self.dev_type == constants.DT_PLAIN:
829
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
830
    elif self.dev_type in constants.DTS_DRBD:
831
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
832
      val = "<DRBD8("
833

    
834
      val += ("hosts=%s/%d-%s/%d, port=%s, " %
835
              (node_a, minor_a, node_b, minor_b, port))
836
      if self.children and self.children.count(None) == 0:
837
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
838
      else:
839
        val += "no local storage"
840
    else:
841
      val = ("<Disk(type=%s, logical_id=%s, children=%s" %
842
             (self.dev_type, self.logical_id, self.children))
843
    if self.iv_name is None:
844
      val += ", not visible"
845
    else:
846
      val += ", visible as /dev/%s" % self.iv_name
847
    if self.spindles is not None:
848
      val += ", spindles=%s" % self.spindles
849
    if isinstance(self.size, int):
850
      val += ", size=%dm)>" % self.size
851
    else:
852
      val += ", size='%s')>" % (self.size,)
853
    return val
854

    
855
  def Verify(self):
856
    """Checks that this disk is correctly configured.
857

858
    """
859
    all_errors = []
860
    if self.mode not in constants.DISK_ACCESS_SET:
861
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
862
    return all_errors
863

    
864
  def UpgradeConfig(self):
865
    """Fill defaults for missing configuration values.
866

867
    """
868
    if self.children:
869
      for child in self.children:
870
        child.UpgradeConfig()
871

    
872
    # FIXME: Make this configurable in Ganeti 2.7
873
    # Params should be an empty dict that gets filled any time needed
874
    # In case of ext template we allow arbitrary params that should not
875
    # be overrided during a config reload/upgrade.
876
    if not self.params or not isinstance(self.params, dict):
877
      self.params = {}
878

    
879
    # add here config upgrade for this disk
880

    
881
    # map of legacy device types (mapping differing LD constants to new
882
    # DT constants)
883
    LEG_DEV_TYPE_MAP = {"lvm": constants.DT_PLAIN, "drbd8": constants.DT_DRBD8}
884
    if self.dev_type in LEG_DEV_TYPE_MAP:
885
      self.dev_type = LEG_DEV_TYPE_MAP[self.dev_type]
886

    
887
  @staticmethod
888
  def ComputeLDParams(disk_template, disk_params):
889
    """Computes Logical Disk parameters from Disk Template parameters.
890

891
    @type disk_template: string
892
    @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
893
    @type disk_params: dict
894
    @param disk_params: disk template parameters;
895
                        dict(template_name -> parameters
896
    @rtype: list(dict)
897
    @return: a list of dicts, one for each node of the disk hierarchy. Each dict
898
      contains the LD parameters of the node. The tree is flattened in-order.
899

900
    """
901
    if disk_template not in constants.DISK_TEMPLATES:
902
      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
903

    
904
    assert disk_template in disk_params
905

    
906
    result = list()
907
    dt_params = disk_params[disk_template]
908

    
909
    if disk_template == constants.DT_DRBD8:
910
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_DRBD8], {
911
        constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
912
        constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
913
        constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
914
        constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
915
        constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
916
        constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
917
        constants.LDP_PROTOCOL: dt_params[constants.DRBD_PROTOCOL],
918
        constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
919
        constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
920
        constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
921
        constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
922
        constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
923
        constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
924
        }))
925

    
926
      # data LV
927
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_PLAIN], {
928
        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
929
        }))
930

    
931
      # metadata LV
932
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_PLAIN], {
933
        constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
934
        }))
935

    
936
    else:
937
      defaults = constants.DISK_LD_DEFAULTS[disk_template]
938
      values = {}
939
      for field in defaults:
940
        values[field] = dt_params[field]
941
      result.append(FillDict(defaults, values))
942

    
943
    return result
944

    
945

    
946
class InstancePolicy(ConfigObject):
947
  """Config object representing instance policy limits dictionary.
948

949
  Note that this object is not actually used in the config, it's just
950
  used as a placeholder for a few functions.
951

952
  """
953
  @classmethod
954
  def UpgradeDiskTemplates(cls, ipolicy, enabled_disk_templates):
955
    """Upgrades the ipolicy configuration."""
956
    if constants.IPOLICY_DTS in ipolicy:
957
      if not set(ipolicy[constants.IPOLICY_DTS]).issubset(
958
        set(enabled_disk_templates)):
959
        ipolicy[constants.IPOLICY_DTS] = list(
960
          set(ipolicy[constants.IPOLICY_DTS]) & set(enabled_disk_templates))
961

    
962
  @classmethod
963
  def CheckParameterSyntax(cls, ipolicy, check_std):
964
    """ Check the instance policy for validity.
965

966
    @type ipolicy: dict
967
    @param ipolicy: dictionary with min/max/std specs and policies
968
    @type check_std: bool
969
    @param check_std: Whether to check std value or just assume compliance
970
    @raise errors.ConfigurationError: when the policy is not legal
971

972
    """
973
    InstancePolicy.CheckISpecSyntax(ipolicy, check_std)
974
    if constants.IPOLICY_DTS in ipolicy:
975
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
976
    for key in constants.IPOLICY_PARAMETERS:
977
      if key in ipolicy:
978
        InstancePolicy.CheckParameter(key, ipolicy[key])
979
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
980
    if wrong_keys:
981
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
982
                                      utils.CommaJoin(wrong_keys))
983

    
984
  @classmethod
985
  def _CheckIncompleteSpec(cls, spec, keyname):
986
    missing_params = constants.ISPECS_PARAMETERS - frozenset(spec.keys())
987
    if missing_params:
988
      msg = ("Missing instance specs parameters for %s: %s" %
989
             (keyname, utils.CommaJoin(missing_params)))
990
      raise errors.ConfigurationError(msg)
991

    
992
  @classmethod
993
  def CheckISpecSyntax(cls, ipolicy, check_std):
994
    """Check the instance policy specs for validity.
995

996
    @type ipolicy: dict
997
    @param ipolicy: dictionary with min/max/std specs
998
    @type check_std: bool
999
    @param check_std: Whether to check std value or just assume compliance
1000
    @raise errors.ConfigurationError: when specs are not valid
1001

1002
    """
1003
    if constants.ISPECS_MINMAX not in ipolicy:
1004
      # Nothing to check
1005
      return
1006

    
1007
    if check_std and constants.ISPECS_STD not in ipolicy:
1008
      msg = "Missing key in ipolicy: %s" % constants.ISPECS_STD
1009
      raise errors.ConfigurationError(msg)
1010
    stdspec = ipolicy.get(constants.ISPECS_STD)
1011
    if check_std:
1012
      InstancePolicy._CheckIncompleteSpec(stdspec, constants.ISPECS_STD)
1013

    
1014
    if not ipolicy[constants.ISPECS_MINMAX]:
1015
      raise errors.ConfigurationError("Empty minmax specifications")
1016
    std_is_good = False
1017
    for minmaxspecs in ipolicy[constants.ISPECS_MINMAX]:
1018
      missing = constants.ISPECS_MINMAX_KEYS - frozenset(minmaxspecs.keys())
1019
      if missing:
1020
        msg = "Missing instance specification: %s" % utils.CommaJoin(missing)
1021
        raise errors.ConfigurationError(msg)
1022
      for (key, spec) in minmaxspecs.items():
1023
        InstancePolicy._CheckIncompleteSpec(spec, key)
1024

    
1025
      spec_std_ok = True
1026
      for param in constants.ISPECS_PARAMETERS:
1027
        par_std_ok = InstancePolicy._CheckISpecParamSyntax(minmaxspecs, stdspec,
1028
                                                           param, check_std)
1029
        spec_std_ok = spec_std_ok and par_std_ok
1030
      std_is_good = std_is_good or spec_std_ok
1031
    if not std_is_good:
1032
      raise errors.ConfigurationError("Invalid std specifications")
1033

    
1034
  @classmethod
1035
  def _CheckISpecParamSyntax(cls, minmaxspecs, stdspec, name, check_std):
1036
    """Check the instance policy specs for validity on a given key.
1037

1038
    We check if the instance specs makes sense for a given key, that is
1039
    if minmaxspecs[min][name] <= stdspec[name] <= minmaxspec[max][name].
1040

1041
    @type minmaxspecs: dict
1042
    @param minmaxspecs: dictionary with min and max instance spec
1043
    @type stdspec: dict
1044
    @param stdspec: dictionary with standard instance spec
1045
    @type name: string
1046
    @param name: what are the limits for
1047
    @type check_std: bool
1048
    @param check_std: Whether to check std value or just assume compliance
1049
    @rtype: bool
1050
    @return: C{True} when specs are valid, C{False} when standard spec for the
1051
        given name is not valid
1052
    @raise errors.ConfigurationError: when min/max specs for the given name
1053
        are not valid
1054

1055
    """
1056
    minspec = minmaxspecs[constants.ISPECS_MIN]
1057
    maxspec = minmaxspecs[constants.ISPECS_MAX]
1058
    min_v = minspec[name]
1059
    max_v = maxspec[name]
1060

    
1061
    if min_v > max_v:
1062
      err = ("Invalid specification of min/max values for %s: %s/%s" %
1063
             (name, min_v, max_v))
1064
      raise errors.ConfigurationError(err)
1065
    elif check_std:
1066
      std_v = stdspec.get(name, min_v)
1067
      return std_v >= min_v and std_v <= max_v
1068
    else:
1069
      return True
1070

    
1071
  @classmethod
1072
  def CheckDiskTemplates(cls, disk_templates):
1073
    """Checks the disk templates for validity.
1074

1075
    """
1076
    if not disk_templates:
1077
      raise errors.ConfigurationError("Instance policy must contain" +
1078
                                      " at least one disk template")
1079
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
1080
    if wrong:
1081
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
1082
                                      utils.CommaJoin(wrong))
1083

    
1084
  @classmethod
1085
  def CheckParameter(cls, key, value):
1086
    """Checks a parameter.
1087

1088
    Currently we expect all parameters to be float values.
1089

1090
    """
1091
    try:
1092
      float(value)
1093
    except (TypeError, ValueError), err:
1094
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
1095
                                      " '%s', error: %s" % (key, value, err))
1096

    
1097

    
1098
def GetOSImage(osparams):
1099
  """Gets the OS image value from the OS parameters.
1100

1101
  @type osparams: L{dict} or NoneType
1102
  @param osparams: OS parameters or None
1103

1104
  @rtype: string or NoneType
1105
  @return:
1106
    value of OS image contained in OS parameters, or None if the OS
1107
    parameters are None or the OS parameters do not contain an OS
1108
    image
1109

1110
  """
1111
  if osparams is None:
1112
    return None
1113
  else:
1114
    return osparams.get("os-image", None)
1115

    
1116

    
1117
def PutOSImage(osparams, os_image):
1118
  """Update OS image value in the OS parameters
1119

1120
  @type osparams: L{dict}
1121
  @param osparams: OS parameters
1122

1123
  @type os_image: string
1124
  @param os_image: OS image
1125

1126
  @rtype: NoneType
1127
  @return: None
1128

1129
  """
1130
  osparams["os-image"] = os_image
1131

    
1132

    
1133
class Instance(TaggableObject):
1134
  """Config object representing an instance."""
1135
  __slots__ = [
1136
    "name",
1137
    "primary_node",
1138
    "os",
1139
    "hypervisor",
1140
    "hvparams",
1141
    "beparams",
1142
    "osparams",
1143
    "osparams_private",
1144
    "admin_state",
1145
    "nics",
1146
    "disks",
1147
    "disk_template",
1148
    "disks_active",
1149
    "network_port",
1150
    "serial_no",
1151
    ] + _TIMESTAMPS + _UUID
1152

    
1153
  def _ComputeSecondaryNodes(self):
1154
    """Compute the list of secondary nodes.
1155

1156
    This is a simple wrapper over _ComputeAllNodes.
1157

1158
    """
1159
    all_nodes = set(self._ComputeAllNodes())
1160
    all_nodes.discard(self.primary_node)
1161
    return tuple(all_nodes)
1162

    
1163
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1164
                             "List of names of secondary nodes")
1165

    
1166
  def _ComputeAllNodes(self):
1167
    """Compute the list of all nodes.
1168

1169
    Since the data is already there (in the drbd disks), keeping it as
1170
    a separate normal attribute is redundant and if not properly
1171
    synchronised can cause problems. Thus it's better to compute it
1172
    dynamically.
1173

1174
    """
1175
    def _Helper(nodes, device):
1176
      """Recursively computes nodes given a top device."""
1177
      if device.dev_type in constants.DTS_DRBD:
1178
        nodea, nodeb = device.logical_id[:2]
1179
        nodes.add(nodea)
1180
        nodes.add(nodeb)
1181
      if device.children:
1182
        for child in device.children:
1183
          _Helper(nodes, child)
1184

    
1185
    all_nodes = set()
1186
    for device in self.disks:
1187
      _Helper(all_nodes, device)
1188
    # ensure that the primary node is always the first
1189
    all_nodes.discard(self.primary_node)
1190
    return (self.primary_node, ) + tuple(all_nodes)
1191

    
1192
  all_nodes = property(_ComputeAllNodes, None, None,
1193
                       "List of names of all the nodes of the instance")
1194

    
1195
  def MapLVsByNode(self, lvmap=None, devs=None, node_uuid=None):
1196
    """Provide a mapping of nodes to LVs this instance owns.
1197

1198
    This function figures out what logical volumes should belong on
1199
    which nodes, recursing through a device tree.
1200

1201
    @type lvmap: dict
1202
    @param lvmap: optional dictionary to receive the
1203
        'node' : ['lv', ...] data.
1204
    @type devs: list of L{Disk}
1205
    @param devs: disks to get the LV name for. If None, all disk of this
1206
        instance are used.
1207
    @type node_uuid: string
1208
    @param node_uuid: UUID of the node to get the LV names for. If None, the
1209
        primary node of this instance is used.
1210
    @return: None if lvmap arg is given, otherwise, a dictionary of
1211
        the form { 'node_uuid' : ['volume1', 'volume2', ...], ... };
1212
        volumeN is of the form "vg_name/lv_name", compatible with
1213
        GetVolumeList()
1214

1215
    """
1216
    if node_uuid is None:
1217
      node_uuid = self.primary_node
1218

    
1219
    if lvmap is None:
1220
      lvmap = {
1221
        node_uuid: [],
1222
        }
1223
      ret = lvmap
1224
    else:
1225
      if not node_uuid in lvmap:
1226
        lvmap[node_uuid] = []
1227
      ret = None
1228

    
1229
    if not devs:
1230
      devs = self.disks
1231

    
1232
    for dev in devs:
1233
      if dev.dev_type == constants.DT_PLAIN:
1234
        lvmap[node_uuid].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1235

    
1236
      elif dev.dev_type in constants.DTS_DRBD:
1237
        if dev.children:
1238
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1239
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1240

    
1241
      elif dev.children:
1242
        self.MapLVsByNode(lvmap, dev.children, node_uuid)
1243

    
1244
    return ret
1245

    
1246
  def FindDisk(self, idx):
1247
    """Find a disk given having a specified index.
1248

1249
    This is just a wrapper that does validation of the index.
1250

1251
    @type idx: int
1252
    @param idx: the disk index
1253
    @rtype: L{Disk}
1254
    @return: the corresponding disk
1255
    @raise errors.OpPrereqError: when the given index is not valid
1256

1257
    """
1258
    try:
1259
      idx = int(idx)
1260
      return self.disks[idx]
1261
    except (TypeError, ValueError), err:
1262
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1263
                                 errors.ECODE_INVAL)
1264
    except IndexError:
1265
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1266
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1267
                                 errors.ECODE_INVAL)
1268

    
1269
  def ToDict(self, _with_private=False):
1270
    """Instance-specific conversion to standard python types.
1271

1272
    This replaces the children lists of objects with lists of standard
1273
    python types.
1274

1275
    """
1276
    bo = super(Instance, self).ToDict(_with_private=_with_private)
1277

    
1278
    if _with_private:
1279
      bo["osparams_private"] = self.osparams_private.Unprivate()
1280

    
1281
    for attr in "nics", "disks":
1282
      alist = bo.get(attr, None)
1283
      if alist:
1284
        nlist = outils.ContainerToDicts(alist)
1285
      else:
1286
        nlist = []
1287
      bo[attr] = nlist
1288
    return bo
1289

    
1290
  @classmethod
1291
  def FromDict(cls, val):
1292
    """Custom function for instances.
1293

1294
    """
1295
    if "admin_state" not in val:
1296
      if val.get("admin_up", False):
1297
        val["admin_state"] = constants.ADMINST_UP
1298
      else:
1299
        val["admin_state"] = constants.ADMINST_DOWN
1300
    if "admin_up" in val:
1301
      del val["admin_up"]
1302
    obj = super(Instance, cls).FromDict(val)
1303
    obj.nics = outils.ContainerFromDicts(obj.nics, list, NIC)
1304
    obj.disks = outils.ContainerFromDicts(obj.disks, list, Disk)
1305
    return obj
1306

    
1307
  def UpgradeConfig(self):
1308
    """Fill defaults for missing configuration values.
1309

1310
    """
1311
    for nic in self.nics:
1312
      nic.UpgradeConfig()
1313
    for disk in self.disks:
1314
      disk.UpgradeConfig()
1315
    if self.hvparams:
1316
      for key in constants.HVC_GLOBALS:
1317
        try:
1318
          del self.hvparams[key]
1319
        except KeyError:
1320
          pass
1321
    if self.osparams is None:
1322
      self.osparams = {}
1323
    if self.osparams_private is None:
1324
      self.osparams_private = serializer.PrivateDict()
1325
    UpgradeBeParams(self.beparams)
1326
    if self.disks_active is None:
1327
      self.disks_active = self.admin_state == constants.ADMINST_UP
1328

    
1329

    
1330
class OS(ConfigObject):
1331
  """Config object representing an operating system.
1332

1333
  @type supported_parameters: list
1334
  @ivar supported_parameters: a list of tuples, name and description,
1335
      containing the supported parameters by this OS
1336

1337
  @type VARIANT_DELIM: string
1338
  @cvar VARIANT_DELIM: the variant delimiter
1339

1340
  """
1341
  __slots__ = [
1342
    "name",
1343
    "path",
1344
    "api_versions",
1345
    "create_script",
1346
    "export_script",
1347
    "import_script",
1348
    "rename_script",
1349
    "verify_script",
1350
    "supported_variants",
1351
    "supported_parameters",
1352
    ]
1353

    
1354
  VARIANT_DELIM = "+"
1355

    
1356
  @classmethod
1357
  def SplitNameVariant(cls, name):
1358
    """Splits the name into the proper name and variant.
1359

1360
    @param name: the OS (unprocessed) name
1361
    @rtype: list
1362
    @return: a list of two elements; if the original name didn't
1363
        contain a variant, it's returned as an empty string
1364

1365
    """
1366
    nv = name.split(cls.VARIANT_DELIM, 1)
1367
    if len(nv) == 1:
1368
      nv.append("")
1369
    return nv
1370

    
1371
  @classmethod
1372
  def GetName(cls, name):
1373
    """Returns the proper name of the os (without the variant).
1374

1375
    @param name: the OS (unprocessed) name
1376

1377
    """
1378
    return cls.SplitNameVariant(name)[0]
1379

    
1380
  @classmethod
1381
  def GetVariant(cls, name):
1382
    """Returns the variant the os (without the base name).
1383

1384
    @param name: the OS (unprocessed) name
1385

1386
    """
1387
    return cls.SplitNameVariant(name)[1]
1388

    
1389

    
1390
class ExtStorage(ConfigObject):
1391
  """Config object representing an External Storage Provider.
1392

1393
  """
1394
  __slots__ = [
1395
    "name",
1396
    "path",
1397
    "create_script",
1398
    "remove_script",
1399
    "grow_script",
1400
    "attach_script",
1401
    "detach_script",
1402
    "setinfo_script",
1403
    "verify_script",
1404
    "supported_parameters",
1405
    ]
1406

    
1407

    
1408
class NodeHvState(ConfigObject):
1409
  """Hypvervisor state on a node.
1410

1411
  @ivar mem_total: Total amount of memory
1412
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1413
    available)
1414
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1415
    rounding
1416
  @ivar mem_inst: Memory used by instances living on node
1417
  @ivar cpu_total: Total node CPU core count
1418
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1419

1420
  """
1421
  __slots__ = [
1422
    "mem_total",
1423
    "mem_node",
1424
    "mem_hv",
1425
    "mem_inst",
1426
    "cpu_total",
1427
    "cpu_node",
1428
    ] + _TIMESTAMPS
1429

    
1430

    
1431
class NodeDiskState(ConfigObject):
1432
  """Disk state on a node.
1433

1434
  """
1435
  __slots__ = [
1436
    "total",
1437
    "reserved",
1438
    "overhead",
1439
    ] + _TIMESTAMPS
1440

    
1441

    
1442
class Node(TaggableObject):
1443
  """Config object representing a node.
1444

1445
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1446
  @ivar hv_state_static: Hypervisor state overriden by user
1447
  @ivar disk_state: Disk state (e.g. free space)
1448
  @ivar disk_state_static: Disk state overriden by user
1449

1450
  """
1451
  __slots__ = [
1452
    "name",
1453
    "primary_ip",
1454
    "secondary_ip",
1455
    "serial_no",
1456
    "master_candidate",
1457
    "offline",
1458
    "drained",
1459
    "group",
1460
    "master_capable",
1461
    "vm_capable",
1462
    "ndparams",
1463
    "powered",
1464
    "hv_state",
1465
    "hv_state_static",
1466
    "disk_state",
1467
    "disk_state_static",
1468
    ] + _TIMESTAMPS + _UUID
1469

    
1470
  def UpgradeConfig(self):
1471
    """Fill defaults for missing configuration values.
1472

1473
    """
1474
    # pylint: disable=E0203
1475
    # because these are "defined" via slots, not manually
1476
    if self.master_capable is None:
1477
      self.master_capable = True
1478

    
1479
    if self.vm_capable is None:
1480
      self.vm_capable = True
1481

    
1482
    if self.ndparams is None:
1483
      self.ndparams = {}
1484
    # And remove any global parameter
1485
    for key in constants.NDC_GLOBALS:
1486
      if key in self.ndparams:
1487
        logging.warning("Ignoring %s node parameter for node %s",
1488
                        key, self.name)
1489
        del self.ndparams[key]
1490

    
1491
    if self.powered is None:
1492
      self.powered = True
1493

    
1494
  def ToDict(self, _with_private=False):
1495
    """Custom function for serializing.
1496

1497
    """
1498
    data = super(Node, self).ToDict(_with_private=_with_private)
1499

    
1500
    hv_state = data.get("hv_state", None)
1501
    if hv_state is not None:
1502
      data["hv_state"] = outils.ContainerToDicts(hv_state)
1503

    
1504
    disk_state = data.get("disk_state", None)
1505
    if disk_state is not None:
1506
      data["disk_state"] = \
1507
        dict((key, outils.ContainerToDicts(value))
1508
             for (key, value) in disk_state.items())
1509

    
1510
    return data
1511

    
1512
  @classmethod
1513
  def FromDict(cls, val):
1514
    """Custom function for deserializing.
1515

1516
    """
1517
    obj = super(Node, cls).FromDict(val)
1518

    
1519
    if obj.hv_state is not None:
1520
      obj.hv_state = \
1521
        outils.ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1522

    
1523
    if obj.disk_state is not None:
1524
      obj.disk_state = \
1525
        dict((key, outils.ContainerFromDicts(value, dict, NodeDiskState))
1526
             for (key, value) in obj.disk_state.items())
1527

    
1528
    return obj
1529

    
1530

    
1531
class NodeGroup(TaggableObject):
1532
  """Config object representing a node group."""
1533
  __slots__ = [
1534
    "name",
1535
    "members",
1536
    "ndparams",
1537
    "diskparams",
1538
    "ipolicy",
1539
    "serial_no",
1540
    "hv_state_static",
1541
    "disk_state_static",
1542
    "alloc_policy",
1543
    "networks",
1544
    ] + _TIMESTAMPS + _UUID
1545

    
1546
  def ToDict(self, _with_private=False):
1547
    """Custom function for nodegroup.
1548

1549
    This discards the members object, which gets recalculated and is only kept
1550
    in memory.
1551

1552
    """
1553
    mydict = super(NodeGroup, self).ToDict(_with_private=_with_private)
1554
    del mydict["members"]
1555
    return mydict
1556

    
1557
  @classmethod
1558
  def FromDict(cls, val):
1559
    """Custom function for nodegroup.
1560

1561
    The members slot is initialized to an empty list, upon deserialization.
1562

1563
    """
1564
    obj = super(NodeGroup, cls).FromDict(val)
1565
    obj.members = []
1566
    return obj
1567

    
1568
  def UpgradeConfig(self):
1569
    """Fill defaults for missing configuration values.
1570

1571
    """
1572
    if self.ndparams is None:
1573
      self.ndparams = {}
1574

    
1575
    if self.serial_no is None:
1576
      self.serial_no = 1
1577

    
1578
    if self.alloc_policy is None:
1579
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1580

    
1581
    # We only update mtime, and not ctime, since we would not be able
1582
    # to provide a correct value for creation time.
1583
    if self.mtime is None:
1584
      self.mtime = time.time()
1585

    
1586
    if self.diskparams is None:
1587
      self.diskparams = {}
1588
    if self.ipolicy is None:
1589
      self.ipolicy = MakeEmptyIPolicy()
1590

    
1591
    if self.networks is None:
1592
      self.networks = {}
1593

    
1594
  def FillND(self, node):
1595
    """Return filled out ndparams for L{objects.Node}
1596

1597
    @type node: L{objects.Node}
1598
    @param node: A Node object to fill
1599
    @return a copy of the node's ndparams with defaults filled
1600

1601
    """
1602
    return self.SimpleFillND(node.ndparams)
1603

    
1604
  def SimpleFillND(self, ndparams):
1605
    """Fill a given ndparams dict with defaults.
1606

1607
    @type ndparams: dict
1608
    @param ndparams: the dict to fill
1609
    @rtype: dict
1610
    @return: a copy of the passed in ndparams with missing keys filled
1611
        from the node group defaults
1612

1613
    """
1614
    return FillDict(self.ndparams, ndparams)
1615

    
1616

    
1617
class Cluster(TaggableObject):
1618
  """Config object representing the cluster."""
1619
  __slots__ = [
1620
    "serial_no",
1621
    "rsahostkeypub",
1622
    "dsahostkeypub",
1623
    "highest_used_port",
1624
    "tcpudp_port_pool",
1625
    "mac_prefix",
1626
    "volume_group_name",
1627
    "reserved_lvs",
1628
    "drbd_usermode_helper",
1629
    "default_bridge",
1630
    "default_hypervisor",
1631
    "master_node",
1632
    "master_ip",
1633
    "master_netdev",
1634
    "master_netmask",
1635
    "use_external_mip_script",
1636
    "cluster_name",
1637
    "file_storage_dir",
1638
    "shared_file_storage_dir",
1639
    "gluster_storage_dir",
1640
    "enabled_hypervisors",
1641
    "hvparams",
1642
    "ipolicy",
1643
    "os_hvp",
1644
    "beparams",
1645
    "osparams",
1646
    "osparams_private_cluster",
1647
    "nicparams",
1648
    "ndparams",
1649
    "diskparams",
1650
    "candidate_pool_size",
1651
    "modify_etc_hosts",
1652
    "modify_ssh_setup",
1653
    "maintain_node_health",
1654
    "uid_pool",
1655
    "default_iallocator",
1656
    "default_iallocator_params",
1657
    "hidden_os",
1658
    "blacklisted_os",
1659
    "primary_ip_family",
1660
    "prealloc_wipe_disks",
1661
    "hv_state_static",
1662
    "disk_state_static",
1663
    "enabled_disk_templates",
1664
    "candidate_certs",
1665
    "max_running_jobs",
1666
    "instance_communication_network",
1667
    ] + _TIMESTAMPS + _UUID
1668

    
1669
  def UpgradeConfig(self):
1670
    """Fill defaults for missing configuration values.
1671

1672
    """
1673
    # pylint: disable=E0203
1674
    # because these are "defined" via slots, not manually
1675
    if self.hvparams is None:
1676
      self.hvparams = constants.HVC_DEFAULTS
1677
    else:
1678
      for hypervisor in constants.HYPER_TYPES:
1679
        try:
1680
          existing_params = self.hvparams[hypervisor]
1681
        except KeyError:
1682
          existing_params = {}
1683
        self.hvparams[hypervisor] = FillDict(
1684
            constants.HVC_DEFAULTS[hypervisor], existing_params)
1685

    
1686
    if self.os_hvp is None:
1687
      self.os_hvp = {}
1688

    
1689
    if self.osparams is None:
1690
      self.osparams = {}
1691
    # osparams_private_cluster added in 2.12
1692
    if self.osparams_private_cluster is None:
1693
      self.osparams_private_cluster = {}
1694

    
1695
    self.ndparams = UpgradeNDParams(self.ndparams)
1696

    
1697
    self.beparams = UpgradeGroupedParams(self.beparams,
1698
                                         constants.BEC_DEFAULTS)
1699
    for beparams_group in self.beparams:
1700
      UpgradeBeParams(self.beparams[beparams_group])
1701

    
1702
    migrate_default_bridge = not self.nicparams
1703
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1704
                                          constants.NICC_DEFAULTS)
1705
    if migrate_default_bridge:
1706
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1707
        self.default_bridge
1708

    
1709
    if self.modify_etc_hosts is None:
1710
      self.modify_etc_hosts = True
1711

    
1712
    if self.modify_ssh_setup is None:
1713
      self.modify_ssh_setup = True
1714

    
1715
    # default_bridge is no longer used in 2.1. The slot is left there to
1716
    # support auto-upgrading. It can be removed once we decide to deprecate
1717
    # upgrading straight from 2.0.
1718
    if self.default_bridge is not None:
1719
      self.default_bridge = None
1720

    
1721
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1722
    # code can be removed once upgrading straight from 2.0 is deprecated.
1723
    if self.default_hypervisor is not None:
1724
      self.enabled_hypervisors = ([self.default_hypervisor] +
1725
                                  [hvname for hvname in self.enabled_hypervisors
1726
                                   if hvname != self.default_hypervisor])
1727
      self.default_hypervisor = None
1728

    
1729
    # maintain_node_health added after 2.1.1
1730
    if self.maintain_node_health is None:
1731
      self.maintain_node_health = False
1732

    
1733
    if self.uid_pool is None:
1734
      self.uid_pool = []
1735

    
1736
    if self.default_iallocator is None:
1737
      self.default_iallocator = ""
1738

    
1739
    if self.default_iallocator_params is None:
1740
      self.default_iallocator_params = {}
1741

    
1742
    # reserved_lvs added before 2.2
1743
    if self.reserved_lvs is None:
1744
      self.reserved_lvs = []
1745

    
1746
    # hidden and blacklisted operating systems added before 2.2.1
1747
    if self.hidden_os is None:
1748
      self.hidden_os = []
1749

    
1750
    if self.blacklisted_os is None:
1751
      self.blacklisted_os = []
1752

    
1753
    # primary_ip_family added before 2.3
1754
    if self.primary_ip_family is None:
1755
      self.primary_ip_family = AF_INET
1756

    
1757
    if self.master_netmask is None:
1758
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1759
      self.master_netmask = ipcls.iplen
1760

    
1761
    if self.prealloc_wipe_disks is None:
1762
      self.prealloc_wipe_disks = False
1763

    
1764
    # shared_file_storage_dir added before 2.5
1765
    if self.shared_file_storage_dir is None:
1766
      self.shared_file_storage_dir = ""
1767

    
1768
    # gluster_storage_dir added in 2.11
1769
    if self.gluster_storage_dir is None:
1770
      self.gluster_storage_dir = ""
1771

    
1772
    if self.use_external_mip_script is None:
1773
      self.use_external_mip_script = False
1774

    
1775
    if self.diskparams:
1776
      self.diskparams = UpgradeDiskParams(self.diskparams)
1777
    else:
1778
      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1779

    
1780
    # instance policy added before 2.6
1781
    if self.ipolicy is None:
1782
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1783
    else:
1784
      # we can either make sure to upgrade the ipolicy always, or only
1785
      # do it in some corner cases (e.g. missing keys); note that this
1786
      # will break any removal of keys from the ipolicy dict
1787
      wrongkeys = frozenset(self.ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
1788
      if wrongkeys:
1789
        # These keys would be silently removed by FillIPolicy()
1790
        msg = ("Cluster instance policy contains spurious keys: %s" %
1791
               utils.CommaJoin(wrongkeys))
1792
        raise errors.ConfigurationError(msg)
1793
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1794

    
1795
    if self.candidate_certs is None:
1796
      self.candidate_certs = {}
1797

    
1798
    if self.max_running_jobs is None:
1799
      self.max_running_jobs = constants.LUXID_MAXIMAL_RUNNING_JOBS_DEFAULT
1800

    
1801
    if self.instance_communication_network is None:
1802
      self.instance_communication_network = ""
1803

    
1804
  @property
1805
  def primary_hypervisor(self):
1806
    """The first hypervisor is the primary.
1807

1808
    Useful, for example, for L{Node}'s hv/disk state.
1809

1810
    """
1811
    return self.enabled_hypervisors[0]
1812

    
1813
  def ToDict(self, _with_private=False):
1814
    """Custom function for cluster.
1815

1816
    """
1817
    mydict = super(Cluster, self).ToDict(_with_private=_with_private)
1818

    
1819
    # Explicitly save private parameters.
1820
    if _with_private:
1821
      for os in mydict["osparams_private_cluster"]:
1822
        mydict["osparams_private_cluster"][os] = \
1823
          self.osparams_private_cluster[os].Unprivate()
1824

    
1825
    if self.tcpudp_port_pool is None:
1826
      tcpudp_port_pool = []
1827
    else:
1828
      tcpudp_port_pool = list(self.tcpudp_port_pool)
1829

    
1830
    mydict["tcpudp_port_pool"] = tcpudp_port_pool
1831

    
1832
    return mydict
1833

    
1834
  @classmethod
1835
  def FromDict(cls, val):
1836
    """Custom function for cluster.
1837

1838
    """
1839
    obj = super(Cluster, cls).FromDict(val)
1840

    
1841
    if obj.tcpudp_port_pool is None:
1842
      obj.tcpudp_port_pool = set()
1843
    elif not isinstance(obj.tcpudp_port_pool, set):
1844
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1845

    
1846
    return obj
1847

    
1848
  def SimpleFillDP(self, diskparams):
1849
    """Fill a given diskparams dict with cluster defaults.
1850

1851
    @param diskparams: The diskparams
1852
    @return: The defaults dict
1853

1854
    """
1855
    return FillDiskParams(self.diskparams, diskparams)
1856

    
1857
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1858
    """Get the default hypervisor parameters for the cluster.
1859

1860
    @param hypervisor: the hypervisor name
1861
    @param os_name: if specified, we'll also update the defaults for this OS
1862
    @param skip_keys: if passed, list of keys not to use
1863
    @return: the defaults dict
1864

1865
    """
1866
    if skip_keys is None:
1867
      skip_keys = []
1868

    
1869
    fill_stack = [self.hvparams.get(hypervisor, {})]
1870
    if os_name is not None:
1871
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1872
      fill_stack.append(os_hvp)
1873

    
1874
    ret_dict = {}
1875
    for o_dict in fill_stack:
1876
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1877

    
1878
    return ret_dict
1879

    
1880
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1881
    """Fill a given hvparams dict with cluster defaults.
1882

1883
    @type hv_name: string
1884
    @param hv_name: the hypervisor to use
1885
    @type os_name: string
1886
    @param os_name: the OS to use for overriding the hypervisor defaults
1887
    @type skip_globals: boolean
1888
    @param skip_globals: if True, the global hypervisor parameters will
1889
        not be filled
1890
    @rtype: dict
1891
    @return: a copy of the given hvparams with missing keys filled from
1892
        the cluster defaults
1893

1894
    """
1895
    if skip_globals:
1896
      skip_keys = constants.HVC_GLOBALS
1897
    else:
1898
      skip_keys = []
1899

    
1900
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1901
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1902

    
1903
  def FillHV(self, instance, skip_globals=False):
1904
    """Fill an instance's hvparams dict with cluster defaults.
1905

1906
    @type instance: L{objects.Instance}
1907
    @param instance: the instance parameter to fill
1908
    @type skip_globals: boolean
1909
    @param skip_globals: if True, the global hypervisor parameters will
1910
        not be filled
1911
    @rtype: dict
1912
    @return: a copy of the instance's hvparams with missing keys filled from
1913
        the cluster defaults
1914

1915
    """
1916
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1917
                             instance.hvparams, skip_globals)
1918

    
1919
  def SimpleFillBE(self, beparams):
1920
    """Fill a given beparams dict with cluster defaults.
1921

1922
    @type beparams: dict
1923
    @param beparams: the dict to fill
1924
    @rtype: dict
1925
    @return: a copy of the passed in beparams with missing keys filled
1926
        from the cluster defaults
1927

1928
    """
1929
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1930

    
1931
  def FillBE(self, instance):
1932
    """Fill an instance's beparams dict with cluster defaults.
1933

1934
    @type instance: L{objects.Instance}
1935
    @param instance: the instance parameter to fill
1936
    @rtype: dict
1937
    @return: a copy of the instance's beparams with missing keys filled from
1938
        the cluster defaults
1939

1940
    """
1941
    return self.SimpleFillBE(instance.beparams)
1942

    
1943
  def SimpleFillNIC(self, nicparams):
1944
    """Fill a given nicparams dict with cluster defaults.
1945

1946
    @type nicparams: dict
1947
    @param nicparams: the dict to fill
1948
    @rtype: dict
1949
    @return: a copy of the passed in nicparams with missing keys filled
1950
        from the cluster defaults
1951

1952
    """
1953
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1954

    
1955
  def SimpleFillOS(self, os_name,
1956
                    os_params_public,
1957
                    os_params_private=None,
1958
                    os_params_secret=None):
1959
    """Fill an instance's osparams dict with cluster defaults.
1960

1961
    @type os_name: string
1962
    @param os_name: the OS name to use
1963
    @type os_params_public: dict
1964
    @param os_params_public: the dict to fill with default values
1965
    @type os_params_private: dict
1966
    @param os_params_private: the dict with private fields to fill
1967
                              with default values. Not passing this field
1968
                              results in no private fields being added to the
1969
                              return value. Private fields will be wrapped in
1970
                              L{Private} objects.
1971
    @type os_params_secret: dict
1972
    @param os_params_secret: the dict with secret fields to fill
1973
                             with default values. Not passing this field
1974
                             results in no secret fields being added to the
1975
                             return value. Private fields will be wrapped in
1976
                             L{Private} objects.
1977
    @rtype: dict
1978
    @return: a copy of the instance's osparams with missing keys filled from
1979
        the cluster defaults. Private and secret parameters are not included
1980
        unless the respective optional parameters are supplied.
1981

1982
    """
1983
    if os_name is None:
1984
      name_only = None
1985
    else:
1986
      name_only = OS.GetName(os_name)
1987

    
1988
    defaults_base_public = self.osparams.get(name_only, {})
1989
    defaults_public = FillDict(defaults_base_public,
1990
                               self.osparams.get(os_name, {}))
1991
    params_public = FillDict(defaults_public, os_params_public)
1992

    
1993
    if os_params_private is not None:
1994
      defaults_base_private = self.osparams_private_cluster.get(name_only, {})
1995
      defaults_private = FillDict(defaults_base_private,
1996
                                  self.osparams_private_cluster.get(os_name,
1997
                                                                    {}))
1998
      params_private = FillDict(defaults_private, os_params_private)
1999
    else:
2000
      params_private = {}
2001

    
2002
    if os_params_secret is not None:
2003
      # There can't be default secret settings, so there's nothing to be done.
2004
      params_secret = os_params_secret
2005
    else:
2006
      params_secret = {}
2007

    
2008
    # Enforce that the set of keys be distinct:
2009
    duplicate_keys = utils.GetRepeatedKeys(params_public,
2010
                                           params_private,
2011
                                           params_secret)
2012
    if not duplicate_keys:
2013

    
2014
      # Actually update them:
2015
      params_public.update(params_private)
2016
      params_public.update(params_secret)
2017

    
2018
      return params_public
2019

    
2020
    else:
2021

    
2022
      def formatter(keys):
2023
        return utils.CommaJoin(sorted(map(repr, keys))) if keys else "(none)"
2024

    
2025
      #Lose the values.
2026
      params_public = set(params_public)
2027
      params_private = set(params_private)
2028
      params_secret = set(params_secret)
2029

    
2030
      msg = """Cannot assign multiple values to OS parameters.
2031

2032
      Conflicting OS parameters that would have been set by this operation:
2033
      - at public visibility:  {public}
2034
      - at private visibility: {private}
2035
      - at secret visibility:  {secret}
2036
      """.format(dupes=formatter(duplicate_keys),
2037
                 public=formatter(params_public & duplicate_keys),
2038
                 private=formatter(params_private & duplicate_keys),
2039
                 secret=formatter(params_secret & duplicate_keys))
2040
      raise errors.OpPrereqError(msg)
2041

    
2042
  @staticmethod
2043
  def SimpleFillHvState(hv_state):
2044
    """Fill an hv_state sub dict with cluster defaults.
2045

2046
    """
2047
    return FillDict(constants.HVST_DEFAULTS, hv_state)
2048

    
2049
  @staticmethod
2050
  def SimpleFillDiskState(disk_state):
2051
    """Fill an disk_state sub dict with cluster defaults.
2052

2053
    """
2054
    return FillDict(constants.DS_DEFAULTS, disk_state)
2055

    
2056
  def FillND(self, node, nodegroup):
2057
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
2058

2059
    @type node: L{objects.Node}
2060
    @param node: A Node object to fill
2061
    @type nodegroup: L{objects.NodeGroup}
2062
    @param nodegroup: A Node object to fill
2063
    @return a copy of the node's ndparams with defaults filled
2064

2065
    """
2066
    return self.SimpleFillND(nodegroup.FillND(node))
2067

    
2068
  def FillNDGroup(self, nodegroup):
2069
    """Return filled out ndparams for just L{objects.NodeGroup}
2070

2071
    @type nodegroup: L{objects.NodeGroup}
2072
    @param nodegroup: A Node object to fill
2073
    @return a copy of the node group's ndparams with defaults filled
2074

2075
    """
2076
    return self.SimpleFillND(nodegroup.SimpleFillND({}))
2077

    
2078
  def SimpleFillND(self, ndparams):
2079
    """Fill a given ndparams dict with defaults.
2080

2081
    @type ndparams: dict
2082
    @param ndparams: the dict to fill
2083
    @rtype: dict
2084
    @return: a copy of the passed in ndparams with missing keys filled
2085
        from the cluster defaults
2086

2087
    """
2088
    return FillDict(self.ndparams, ndparams)
2089

    
2090
  def SimpleFillIPolicy(self, ipolicy):
2091
    """ Fill instance policy dict with defaults.
2092

2093
    @type ipolicy: dict
2094
    @param ipolicy: the dict to fill
2095
    @rtype: dict
2096
    @return: a copy of passed ipolicy with missing keys filled from
2097
      the cluster defaults
2098

2099
    """
2100
    return FillIPolicy(self.ipolicy, ipolicy)
2101

    
2102
  def IsDiskTemplateEnabled(self, disk_template):
2103
    """Checks if a particular disk template is enabled.
2104

2105
    """
2106
    return utils.storage.IsDiskTemplateEnabled(
2107
        disk_template, self.enabled_disk_templates)
2108

    
2109
  def IsFileStorageEnabled(self):
2110
    """Checks if file storage is enabled.
2111

2112
    """
2113
    return utils.storage.IsFileStorageEnabled(self.enabled_disk_templates)
2114

    
2115
  def IsSharedFileStorageEnabled(self):
2116
    """Checks if shared file storage is enabled.
2117

2118
    """
2119
    return utils.storage.IsSharedFileStorageEnabled(
2120
        self.enabled_disk_templates)
2121

    
2122

    
2123
class BlockDevStatus(ConfigObject):
2124
  """Config object representing the status of a block device."""
2125
  __slots__ = [
2126
    "dev_path",
2127
    "major",
2128
    "minor",
2129
    "sync_percent",
2130
    "estimated_time",
2131
    "is_degraded",
2132
    "ldisk_status",
2133
    ]
2134

    
2135

    
2136
class ImportExportStatus(ConfigObject):
2137
  """Config object representing the status of an import or export."""
2138
  __slots__ = [
2139
    "recent_output",
2140
    "listen_port",
2141
    "connected",
2142
    "progress_mbytes",
2143
    "progress_throughput",
2144
    "progress_eta",
2145
    "progress_percent",
2146
    "exit_status",
2147
    "error_message",
2148
    ] + _TIMESTAMPS
2149

    
2150

    
2151
class ImportExportOptions(ConfigObject):
2152
  """Options for import/export daemon
2153

2154
  @ivar key_name: X509 key name (None for cluster certificate)
2155
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
2156
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
2157
  @ivar magic: Used to ensure the connection goes to the right disk
2158
  @ivar ipv6: Whether to use IPv6
2159
  @ivar connect_timeout: Number of seconds for establishing connection
2160

2161
  """
2162
  __slots__ = [
2163
    "key_name",
2164
    "ca_pem",
2165
    "compress",
2166
    "magic",
2167
    "ipv6",
2168
    "connect_timeout",
2169
    ]
2170

    
2171

    
2172
class ConfdRequest(ConfigObject):
2173
  """Object holding a confd request.
2174

2175
  @ivar protocol: confd protocol version
2176
  @ivar type: confd query type
2177
  @ivar query: query request
2178
  @ivar rsalt: requested reply salt
2179

2180
  """
2181
  __slots__ = [
2182
    "protocol",
2183
    "type",
2184
    "query",
2185
    "rsalt",
2186
    ]
2187

    
2188

    
2189
class ConfdReply(ConfigObject):
2190
  """Object holding a confd reply.
2191

2192
  @ivar protocol: confd protocol version
2193
  @ivar status: reply status code (ok, error)
2194
  @ivar answer: confd query reply
2195
  @ivar serial: configuration serial number
2196

2197
  """
2198
  __slots__ = [
2199
    "protocol",
2200
    "status",
2201
    "answer",
2202
    "serial",
2203
    ]
2204

    
2205

    
2206
class QueryFieldDefinition(ConfigObject):
2207
  """Object holding a query field definition.
2208

2209
  @ivar name: Field name
2210
  @ivar title: Human-readable title
2211
  @ivar kind: Field type
2212
  @ivar doc: Human-readable description
2213

2214
  """
2215
  __slots__ = [
2216
    "name",
2217
    "title",
2218
    "kind",
2219
    "doc",
2220
    ]
2221

    
2222

    
2223
class _QueryResponseBase(ConfigObject):
2224
  __slots__ = [
2225
    "fields",
2226
    ]
2227

    
2228
  def ToDict(self, _with_private=False):
2229
    """Custom function for serializing.
2230

2231
    """
2232
    mydict = super(_QueryResponseBase, self).ToDict()
2233
    mydict["fields"] = outils.ContainerToDicts(mydict["fields"])
2234
    return mydict
2235

    
2236
  @classmethod
2237
  def FromDict(cls, val):
2238
    """Custom function for de-serializing.
2239

2240
    """
2241
    obj = super(_QueryResponseBase, cls).FromDict(val)
2242
    obj.fields = \
2243
      outils.ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
2244
    return obj
2245

    
2246

    
2247
class QueryResponse(_QueryResponseBase):
2248
  """Object holding the response to a query.
2249

2250
  @ivar fields: List of L{QueryFieldDefinition} objects
2251
  @ivar data: Requested data
2252

2253
  """
2254
  __slots__ = [
2255
    "data",
2256
    ]
2257

    
2258

    
2259
class QueryFieldsRequest(ConfigObject):
2260
  """Object holding a request for querying available fields.
2261

2262
  """
2263
  __slots__ = [
2264
    "what",
2265
    "fields",
2266
    ]
2267

    
2268

    
2269
class QueryFieldsResponse(_QueryResponseBase):
2270
  """Object holding the response to a query for fields.
2271

2272
  @ivar fields: List of L{QueryFieldDefinition} objects
2273

2274
  """
2275
  __slots__ = []
2276

    
2277

    
2278
class MigrationStatus(ConfigObject):
2279
  """Object holding the status of a migration.
2280

2281
  """
2282
  __slots__ = [
2283
    "status",
2284
    "transferred_ram",
2285
    "total_ram",
2286
    ]
2287

    
2288

    
2289
class InstanceConsole(ConfigObject):
2290
  """Object describing how to access the console of an instance.
2291

2292
  """
2293
  __slots__ = [
2294
    "instance",
2295
    "kind",
2296
    "message",
2297
    "host",
2298
    "port",
2299
    "user",
2300
    "command",
2301
    "display",
2302
    ]
2303

    
2304
  def Validate(self):
2305
    """Validates contents of this object.
2306

2307
    """
2308
    assert self.kind in constants.CONS_ALL, "Unknown console type"
2309
    assert self.instance, "Missing instance name"
2310
    assert self.message or self.kind in [constants.CONS_SSH,
2311
                                         constants.CONS_SPICE,
2312
                                         constants.CONS_VNC]
2313
    assert self.host or self.kind == constants.CONS_MESSAGE
2314
    assert self.port or self.kind in [constants.CONS_MESSAGE,
2315
                                      constants.CONS_SSH]
2316
    assert self.user or self.kind in [constants.CONS_MESSAGE,
2317
                                      constants.CONS_SPICE,
2318
                                      constants.CONS_VNC]
2319
    assert self.command or self.kind in [constants.CONS_MESSAGE,
2320
                                         constants.CONS_SPICE,
2321
                                         constants.CONS_VNC]
2322
    assert self.display or self.kind in [constants.CONS_MESSAGE,
2323
                                         constants.CONS_SPICE,
2324
                                         constants.CONS_SSH]
2325

    
2326

    
2327
class Network(TaggableObject):
2328
  """Object representing a network definition for ganeti.
2329

2330
  """
2331
  __slots__ = [
2332
    "name",
2333
    "serial_no",
2334
    "mac_prefix",
2335
    "network",
2336
    "network6",
2337
    "gateway",
2338
    "gateway6",
2339
    "reservations",
2340
    "ext_reservations",
2341
    ] + _TIMESTAMPS + _UUID
2342

    
2343
  def HooksDict(self, prefix=""):
2344
    """Export a dictionary used by hooks with a network's information.
2345

2346
    @type prefix: String
2347
    @param prefix: Prefix to prepend to the dict entries
2348

2349
    """
2350
    result = {
2351
      "%sNETWORK_NAME" % prefix: self.name,
2352
      "%sNETWORK_UUID" % prefix: self.uuid,
2353
      "%sNETWORK_TAGS" % prefix: " ".join(self.GetTags()),
2354
    }
2355
    if self.network:
2356
      result["%sNETWORK_SUBNET" % prefix] = self.network
2357
    if self.gateway:
2358
      result["%sNETWORK_GATEWAY" % prefix] = self.gateway
2359
    if self.network6:
2360
      result["%sNETWORK_SUBNET6" % prefix] = self.network6
2361
    if self.gateway6:
2362
      result["%sNETWORK_GATEWAY6" % prefix] = self.gateway6
2363
    if self.mac_prefix:
2364
      result["%sNETWORK_MAC_PREFIX" % prefix] = self.mac_prefix
2365

    
2366
    return result
2367

    
2368
  @classmethod
2369
  def FromDict(cls, val):
2370
    """Custom function for networks.
2371

2372
    Remove deprecated network_type and family.
2373

2374
    """
2375
    if "network_type" in val:
2376
      del val["network_type"]
2377
    if "family" in val:
2378
      del val["family"]
2379
    obj = super(Network, cls).FromDict(val)
2380
    return obj
2381

    
2382

    
2383
# need to inherit object in order to use super()
2384
class SerializableConfigParser(ConfigParser.SafeConfigParser, object):
2385
  """Simple wrapper over ConfigParse that allows serialization.
2386

2387
  This class is basically ConfigParser.SafeConfigParser with two
2388
  additional methods that allow it to serialize/unserialize to/from a
2389
  buffer.
2390

2391
  """
2392
  def Dumps(self):
2393
    """Dump this instance and return the string representation."""
2394
    buf = StringIO()
2395
    self.write(buf)
2396
    return buf.getvalue()
2397

    
2398
  @classmethod
2399
  def Loads(cls, data):
2400
    """Load data from a string."""
2401
    buf = StringIO(data)
2402
    cfp = cls()
2403
    cfp.readfp(buf)
2404
    return cfp
2405

    
2406
  def get(self, section, option, **kwargs):
2407
    value = None
2408
    try:
2409
      value = super(SerializableConfigParser, self).get(section, option,
2410
                                                        **kwargs)
2411
      if value.lower() == constants.VALUE_NONE:
2412
        value = None
2413
    except ConfigParser.NoOptionError:
2414
      r = re.compile(r"(disk|nic)\d+_name|nic\d+_(network|vlan)")
2415
      match = r.match(option)
2416
      if match:
2417
        pass
2418
      else:
2419
        raise
2420

    
2421
    return value
2422

    
2423

    
2424
class LvmPvInfo(ConfigObject):
2425
  """Information about an LVM physical volume (PV).
2426

2427
  @type name: string
2428
  @ivar name: name of the PV
2429
  @type vg_name: string
2430
  @ivar vg_name: name of the volume group containing the PV
2431
  @type size: float
2432
  @ivar size: size of the PV in MiB
2433
  @type free: float
2434
  @ivar free: free space in the PV, in MiB
2435
  @type attributes: string
2436
  @ivar attributes: PV attributes
2437
  @type lv_list: list of strings
2438
  @ivar lv_list: names of the LVs hosted on the PV
2439
  """
2440
  __slots__ = [
2441
    "name",
2442
    "vg_name",
2443
    "size",
2444
    "free",
2445
    "attributes",
2446
    "lv_list"
2447
    ]
2448

    
2449
  def IsEmpty(self):
2450
    """Is this PV empty?
2451

2452
    """
2453
    return self.size <= (self.free + 1)
2454

    
2455
  def IsAllocatable(self):
2456
    """Is this PV allocatable?
2457

2458
    """
2459
    return ("a" in self.attributes)