Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 1c4910f7

History | View | Annotate | Download (71.9 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
    "serial_no",
409
    ] + _TIMESTAMPS
410

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

414
    This just replaces the list of instances, nodes and the cluster
415
    with standard python types.
416

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

    
423
    return mydict
424

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

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

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

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

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

    
455
  def UpgradeConfig(self):
456
    """Fill defaults for missing configuration values.
457

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

    
479
  def _UpgradeEnabledDiskTemplates(self):
480
    """Upgrade the cluster's enabled disk templates by inspecting the currently
481
       enabled and/or used disk templates.
482

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

    
503

    
504
class NIC(ConfigObject):
505
  """Config object representing a network card."""
506
  __slots__ = ["name", "mac", "ip", "network",
507
               "nicparams", "netinfo", "pci"] + _UUID
508

    
509
  @classmethod
510
  def CheckParameterSyntax(cls, nicparams):
511
    """Check the given parameters for validity.
512

513
    @type nicparams:  dict
514
    @param nicparams: dictionary with parameter names/value
515
    @raise errors.ConfigurationError: when a parameter is not valid
516

517
    """
518
    mode = nicparams[constants.NIC_MODE]
519
    if (mode not in constants.NIC_VALID_MODES and
520
        mode != constants.VALUE_AUTO):
521
      raise errors.ConfigurationError("Invalid NIC mode '%s'" % mode)
522

    
523
    if (mode == constants.NIC_MODE_BRIDGED and
524
        not nicparams[constants.NIC_LINK]):
525
      raise errors.ConfigurationError("Missing bridged NIC link")
526

    
527

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

    
536
  def CreateOnSecondary(self):
537
    """Test if this device needs to be created on a secondary node."""
538
    return self.dev_type in (constants.DT_DRBD8, constants.DT_PLAIN)
539

    
540
  def AssembleOnSecondary(self):
541
    """Test if this device needs to be assembled on a secondary node."""
542
    return self.dev_type in (constants.DT_DRBD8, constants.DT_PLAIN)
543

    
544
  def OpenOnSecondary(self):
545
    """Test if this device needs to be opened on a secondary node."""
546
    return self.dev_type in (constants.DT_PLAIN,)
547

    
548
  def StaticDevPath(self):
549
    """Return the device path if this device type has a static one.
550

551
    Some devices (LVM for example) live always at the same /dev/ path,
552
    irrespective of their status. For such devices, we return this
553
    path, for others we return None.
554

555
    @warning: The path returned is not a normalized pathname; callers
556
        should check that it is a valid path.
557

558
    """
559
    if self.dev_type == constants.DT_PLAIN:
560
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
561
    elif self.dev_type == constants.DT_BLOCK:
562
      return self.logical_id[1]
563
    elif self.dev_type == constants.DT_RBD:
564
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
565
    return None
566

    
567
  def ChildrenNeeded(self):
568
    """Compute the needed number of children for activation.
569

570
    This method will return either -1 (all children) or a positive
571
    number denoting the minimum number of children needed for
572
    activation (only mirrored devices will usually return >=0).
573

574
    Currently, only DRBD8 supports diskless activation (therefore we
575
    return 0), for all other we keep the previous semantics and return
576
    -1.
577

578
    """
579
    if self.dev_type == constants.DT_DRBD8:
580
      return 0
581
    return -1
582

    
583
  def IsBasedOnDiskType(self, dev_type):
584
    """Check if the disk or its children are based on the given type.
585

586
    @type dev_type: L{constants.DTS_BLOCK}
587
    @param dev_type: the type to look for
588
    @rtype: boolean
589
    @return: boolean indicating if a device of the given type was found or not
590

591
    """
592
    if self.children:
593
      for child in self.children:
594
        if child.IsBasedOnDiskType(dev_type):
595
          return True
596
    return self.dev_type == dev_type
597

    
598
  def GetNodes(self, node_uuid):
599
    """This function returns the nodes this device lives on.
600

601
    Given the node on which the parent of the device lives on (or, in
602
    case of a top-level device, the primary node of the devices'
603
    instance), this function will return a list of nodes on which this
604
    devices needs to (or can) be assembled.
605

606
    """
607
    if self.dev_type in [constants.DT_PLAIN, constants.DT_FILE,
608
                         constants.DT_BLOCK, constants.DT_RBD,
609
                         constants.DT_EXT, constants.DT_SHARED_FILE,
610
                         constants.DT_GLUSTER]:
611
      result = [node_uuid]
612
    elif self.dev_type in constants.DTS_DRBD:
613
      result = [self.logical_id[0], self.logical_id[1]]
614
      if node_uuid not in result:
615
        raise errors.ConfigurationError("DRBD device passed unknown node")
616
    else:
617
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
618
    return result
619

    
620
  def ComputeNodeTree(self, parent_node_uuid):
621
    """Compute the node/disk tree for this disk and its children.
622

623
    This method, given the node on which the parent disk lives, will
624
    return the list of all (node UUID, disk) pairs which describe the disk
625
    tree in the most compact way. For example, a drbd/lvm stack
626
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
627
    which represents all the top-level devices on the nodes.
628

629
    """
630
    my_nodes = self.GetNodes(parent_node_uuid)
631
    result = [(node, self) for node in my_nodes]
632
    if not self.children:
633
      # leaf device
634
      return result
635
    for node in my_nodes:
636
      for child in self.children:
637
        child_result = child.ComputeNodeTree(node)
638
        if len(child_result) == 1:
639
          # child (and all its descendants) is simple, doesn't split
640
          # over multiple hosts, so we don't need to describe it, our
641
          # own entry for this node describes it completely
642
          continue
643
        else:
644
          # check if child nodes differ from my nodes; note that
645
          # subdisk can differ from the child itself, and be instead
646
          # one of its descendants
647
          for subnode, subdisk in child_result:
648
            if subnode not in my_nodes:
649
              result.append((subnode, subdisk))
650
            # otherwise child is under our own node, so we ignore this
651
            # entry (but probably the other results in the list will
652
            # be different)
653
    return result
654

    
655
  def ComputeGrowth(self, amount):
656
    """Compute the per-VG growth requirements.
657

658
    This only works for VG-based disks.
659

660
    @type amount: integer
661
    @param amount: the desired increase in (user-visible) disk space
662
    @rtype: dict
663
    @return: a dictionary of volume-groups and the required size
664

665
    """
666
    if self.dev_type == constants.DT_PLAIN:
667
      return {self.logical_id[0]: amount}
668
    elif self.dev_type == constants.DT_DRBD8:
669
      if self.children:
670
        return self.children[0].ComputeGrowth(amount)
671
      else:
672
        return {}
673
    else:
674
      # Other disk types do not require VG space
675
      return {}
676

    
677
  def RecordGrow(self, amount):
678
    """Update the size of this disk after growth.
679

680
    This method recurses over the disks's children and updates their
681
    size correspondigly. The method needs to be kept in sync with the
682
    actual algorithms from bdev.
683

684
    """
685
    if self.dev_type in (constants.DT_PLAIN, constants.DT_FILE,
686
                         constants.DT_RBD, constants.DT_EXT,
687
                         constants.DT_SHARED_FILE, constants.DT_GLUSTER):
688
      self.size += amount
689
    elif self.dev_type == constants.DT_DRBD8:
690
      if self.children:
691
        self.children[0].RecordGrow(amount)
692
      self.size += amount
693
    else:
694
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
695
                                   " disk type %s" % self.dev_type)
696

    
697
  def Update(self, size=None, mode=None, spindles=None):
698
    """Apply changes to size, spindles and mode.
699

700
    """
701
    if self.dev_type == constants.DT_DRBD8:
702
      if self.children:
703
        self.children[0].Update(size=size, mode=mode)
704
    else:
705
      assert not self.children
706

    
707
    if size is not None:
708
      self.size = size
709
    if mode is not None:
710
      self.mode = mode
711
    if spindles is not None:
712
      self.spindles = spindles
713

    
714
  def UnsetSize(self):
715
    """Sets recursively the size to zero for the disk and its children.
716

717
    """
718
    if self.children:
719
      for child in self.children:
720
        child.UnsetSize()
721
    self.size = 0
722

    
723
  def UpdateDynamicDiskParams(self, target_node_uuid, nodes_ip):
724
    """Updates the dynamic disk params for the given node.
725

726
    This is mainly used for drbd, which needs ip/port configuration.
727

728
    Arguments:
729
      - target_node_uuid: the node UUID we wish to configure for
730
      - nodes_ip: a mapping of node name to ip
731

732
    The target_node must exist in nodes_ip, and should be one of the
733
    nodes in the logical ID if this device is a DRBD device.
734

735
    """
736
    if self.children:
737
      for child in self.children:
738
        child.UpdateDynamicDiskParams(target_node_uuid, nodes_ip)
739

    
740
    dyn_disk_params = {}
741
    if self.logical_id is not None and self.dev_type in constants.DTS_DRBD:
742
      pnode_uuid, snode_uuid, _, pminor, sminor, _ = self.logical_id
743
      if target_node_uuid not in (pnode_uuid, snode_uuid):
744
        # disk object is being sent to neither the primary nor the secondary
745
        # node. reset the dynamic parameters, the target node is not
746
        # supposed to use them.
747
        self.dynamic_params = dyn_disk_params
748
        return
749

    
750
      pnode_ip = nodes_ip.get(pnode_uuid, None)
751
      snode_ip = nodes_ip.get(snode_uuid, None)
752
      if pnode_ip is None or snode_ip is None:
753
        raise errors.ConfigurationError("Can't find primary or secondary node"
754
                                        " for %s" % str(self))
755
      if pnode_uuid == target_node_uuid:
756
        dyn_disk_params[constants.DDP_LOCAL_IP] = pnode_ip
757
        dyn_disk_params[constants.DDP_REMOTE_IP] = snode_ip
758
        dyn_disk_params[constants.DDP_LOCAL_MINOR] = pminor
759
        dyn_disk_params[constants.DDP_REMOTE_MINOR] = sminor
760
      else: # it must be secondary, we tested above
761
        dyn_disk_params[constants.DDP_LOCAL_IP] = snode_ip
762
        dyn_disk_params[constants.DDP_REMOTE_IP] = pnode_ip
763
        dyn_disk_params[constants.DDP_LOCAL_MINOR] = sminor
764
        dyn_disk_params[constants.DDP_REMOTE_MINOR] = pminor
765

    
766
    self.dynamic_params = dyn_disk_params
767

    
768
  # pylint: disable=W0221
769
  def ToDict(self, include_dynamic_params=False,
770
             _with_private=False):
771
    """Disk-specific conversion to standard python types.
772

773
    This replaces the children lists of objects with lists of
774
    standard python types.
775

776
    """
777
    bo = super(Disk, self).ToDict()
778
    if not include_dynamic_params and "dynamic_params" in bo:
779
      del bo["dynamic_params"]
780

    
781
    for attr in ("children",):
782
      alist = bo.get(attr, None)
783
      if alist:
784
        bo[attr] = outils.ContainerToDicts(alist)
785
    return bo
786

    
787
  @classmethod
788
  def FromDict(cls, val):
789
    """Custom function for Disks
790

791
    """
792
    obj = super(Disk, cls).FromDict(val)
793
    if obj.children:
794
      obj.children = outils.ContainerFromDicts(obj.children, list, Disk)
795
    if obj.logical_id and isinstance(obj.logical_id, list):
796
      obj.logical_id = tuple(obj.logical_id)
797
    if obj.dev_type in constants.DTS_DRBD:
798
      # we need a tuple of length six here
799
      if len(obj.logical_id) < 6:
800
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
801
    return obj
802

    
803
  def __str__(self):
804
    """Custom str() formatter for disks.
805

806
    """
807
    if self.dev_type == constants.DT_PLAIN:
808
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
809
    elif self.dev_type in constants.DTS_DRBD:
810
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
811
      val = "<DRBD8("
812

    
813
      val += ("hosts=%s/%d-%s/%d, port=%s, " %
814
              (node_a, minor_a, node_b, minor_b, port))
815
      if self.children and self.children.count(None) == 0:
816
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
817
      else:
818
        val += "no local storage"
819
    else:
820
      val = ("<Disk(type=%s, logical_id=%s, children=%s" %
821
             (self.dev_type, self.logical_id, self.children))
822
    if self.iv_name is None:
823
      val += ", not visible"
824
    else:
825
      val += ", visible as /dev/%s" % self.iv_name
826
    if self.spindles is not None:
827
      val += ", spindles=%s" % self.spindles
828
    if isinstance(self.size, int):
829
      val += ", size=%dm)>" % self.size
830
    else:
831
      val += ", size='%s')>" % (self.size,)
832
    return val
833

    
834
  def Verify(self):
835
    """Checks that this disk is correctly configured.
836

837
    """
838
    all_errors = []
839
    if self.mode not in constants.DISK_ACCESS_SET:
840
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
841
    return all_errors
842

    
843
  def UpgradeConfig(self):
844
    """Fill defaults for missing configuration values.
845

846
    """
847
    if self.children:
848
      for child in self.children:
849
        child.UpgradeConfig()
850

    
851
    # FIXME: Make this configurable in Ganeti 2.7
852
    # Params should be an empty dict that gets filled any time needed
853
    # In case of ext template we allow arbitrary params that should not
854
    # be overrided during a config reload/upgrade.
855
    if not self.params or not isinstance(self.params, dict):
856
      self.params = {}
857

    
858
    # add here config upgrade for this disk
859

    
860
    # map of legacy device types (mapping differing LD constants to new
861
    # DT constants)
862
    LEG_DEV_TYPE_MAP = {"lvm": constants.DT_PLAIN, "drbd8": constants.DT_DRBD8}
863
    if self.dev_type in LEG_DEV_TYPE_MAP:
864
      self.dev_type = LEG_DEV_TYPE_MAP[self.dev_type]
865

    
866
  @staticmethod
867
  def ComputeLDParams(disk_template, disk_params):
868
    """Computes Logical Disk parameters from Disk Template parameters.
869

870
    @type disk_template: string
871
    @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
872
    @type disk_params: dict
873
    @param disk_params: disk template parameters;
874
                        dict(template_name -> parameters
875
    @rtype: list(dict)
876
    @return: a list of dicts, one for each node of the disk hierarchy. Each dict
877
      contains the LD parameters of the node. The tree is flattened in-order.
878

879
    """
880
    if disk_template not in constants.DISK_TEMPLATES:
881
      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
882

    
883
    assert disk_template in disk_params
884

    
885
    result = list()
886
    dt_params = disk_params[disk_template]
887

    
888
    if disk_template == constants.DT_DRBD8:
889
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_DRBD8], {
890
        constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
891
        constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
892
        constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
893
        constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
894
        constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
895
        constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
896
        constants.LDP_PROTOCOL: dt_params[constants.DRBD_PROTOCOL],
897
        constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
898
        constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
899
        constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
900
        constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
901
        constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
902
        constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
903
        }))
904

    
905
      # data LV
906
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_PLAIN], {
907
        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
908
        }))
909

    
910
      # metadata LV
911
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_PLAIN], {
912
        constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
913
        }))
914

    
915
    else:
916
      defaults = constants.DISK_LD_DEFAULTS[disk_template]
917
      values = {}
918
      for field in defaults:
919
        values[field] = dt_params[field]
920
      result.append(FillDict(defaults, values))
921

    
922
    return result
923

    
924

    
925
class InstancePolicy(ConfigObject):
926
  """Config object representing instance policy limits dictionary.
927

928
  Note that this object is not actually used in the config, it's just
929
  used as a placeholder for a few functions.
930

931
  """
932
  @classmethod
933
  def UpgradeDiskTemplates(cls, ipolicy, enabled_disk_templates):
934
    """Upgrades the ipolicy configuration."""
935
    if constants.IPOLICY_DTS in ipolicy:
936
      if not set(ipolicy[constants.IPOLICY_DTS]).issubset(
937
        set(enabled_disk_templates)):
938
        ipolicy[constants.IPOLICY_DTS] = list(
939
          set(ipolicy[constants.IPOLICY_DTS]) & set(enabled_disk_templates))
940

    
941
  @classmethod
942
  def CheckParameterSyntax(cls, ipolicy, check_std):
943
    """ Check the instance policy for validity.
944

945
    @type ipolicy: dict
946
    @param ipolicy: dictionary with min/max/std specs and policies
947
    @type check_std: bool
948
    @param check_std: Whether to check std value or just assume compliance
949
    @raise errors.ConfigurationError: when the policy is not legal
950

951
    """
952
    InstancePolicy.CheckISpecSyntax(ipolicy, check_std)
953
    if constants.IPOLICY_DTS in ipolicy:
954
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
955
    for key in constants.IPOLICY_PARAMETERS:
956
      if key in ipolicy:
957
        InstancePolicy.CheckParameter(key, ipolicy[key])
958
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
959
    if wrong_keys:
960
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
961
                                      utils.CommaJoin(wrong_keys))
962

    
963
  @classmethod
964
  def _CheckIncompleteSpec(cls, spec, keyname):
965
    missing_params = constants.ISPECS_PARAMETERS - frozenset(spec.keys())
966
    if missing_params:
967
      msg = ("Missing instance specs parameters for %s: %s" %
968
             (keyname, utils.CommaJoin(missing_params)))
969
      raise errors.ConfigurationError(msg)
970

    
971
  @classmethod
972
  def CheckISpecSyntax(cls, ipolicy, check_std):
973
    """Check the instance policy specs for validity.
974

975
    @type ipolicy: dict
976
    @param ipolicy: dictionary with min/max/std specs
977
    @type check_std: bool
978
    @param check_std: Whether to check std value or just assume compliance
979
    @raise errors.ConfigurationError: when specs are not valid
980

981
    """
982
    if constants.ISPECS_MINMAX not in ipolicy:
983
      # Nothing to check
984
      return
985

    
986
    if check_std and constants.ISPECS_STD not in ipolicy:
987
      msg = "Missing key in ipolicy: %s" % constants.ISPECS_STD
988
      raise errors.ConfigurationError(msg)
989
    stdspec = ipolicy.get(constants.ISPECS_STD)
990
    if check_std:
991
      InstancePolicy._CheckIncompleteSpec(stdspec, constants.ISPECS_STD)
992

    
993
    if not ipolicy[constants.ISPECS_MINMAX]:
994
      raise errors.ConfigurationError("Empty minmax specifications")
995
    std_is_good = False
996
    for minmaxspecs in ipolicy[constants.ISPECS_MINMAX]:
997
      missing = constants.ISPECS_MINMAX_KEYS - frozenset(minmaxspecs.keys())
998
      if missing:
999
        msg = "Missing instance specification: %s" % utils.CommaJoin(missing)
1000
        raise errors.ConfigurationError(msg)
1001
      for (key, spec) in minmaxspecs.items():
1002
        InstancePolicy._CheckIncompleteSpec(spec, key)
1003

    
1004
      spec_std_ok = True
1005
      for param in constants.ISPECS_PARAMETERS:
1006
        par_std_ok = InstancePolicy._CheckISpecParamSyntax(minmaxspecs, stdspec,
1007
                                                           param, check_std)
1008
        spec_std_ok = spec_std_ok and par_std_ok
1009
      std_is_good = std_is_good or spec_std_ok
1010
    if not std_is_good:
1011
      raise errors.ConfigurationError("Invalid std specifications")
1012

    
1013
  @classmethod
1014
  def _CheckISpecParamSyntax(cls, minmaxspecs, stdspec, name, check_std):
1015
    """Check the instance policy specs for validity on a given key.
1016

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

1020
    @type minmaxspecs: dict
1021
    @param minmaxspecs: dictionary with min and max instance spec
1022
    @type stdspec: dict
1023
    @param stdspec: dictionary with standard instance spec
1024
    @type name: string
1025
    @param name: what are the limits for
1026
    @type check_std: bool
1027
    @param check_std: Whether to check std value or just assume compliance
1028
    @rtype: bool
1029
    @return: C{True} when specs are valid, C{False} when standard spec for the
1030
        given name is not valid
1031
    @raise errors.ConfigurationError: when min/max specs for the given name
1032
        are not valid
1033

1034
    """
1035
    minspec = minmaxspecs[constants.ISPECS_MIN]
1036
    maxspec = minmaxspecs[constants.ISPECS_MAX]
1037
    min_v = minspec[name]
1038
    max_v = maxspec[name]
1039

    
1040
    if min_v > max_v:
1041
      err = ("Invalid specification of min/max values for %s: %s/%s" %
1042
             (name, min_v, max_v))
1043
      raise errors.ConfigurationError(err)
1044
    elif check_std:
1045
      std_v = stdspec.get(name, min_v)
1046
      return std_v >= min_v and std_v <= max_v
1047
    else:
1048
      return True
1049

    
1050
  @classmethod
1051
  def CheckDiskTemplates(cls, disk_templates):
1052
    """Checks the disk templates for validity.
1053

1054
    """
1055
    if not disk_templates:
1056
      raise errors.ConfigurationError("Instance policy must contain" +
1057
                                      " at least one disk template")
1058
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
1059
    if wrong:
1060
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
1061
                                      utils.CommaJoin(wrong))
1062

    
1063
  @classmethod
1064
  def CheckParameter(cls, key, value):
1065
    """Checks a parameter.
1066

1067
    Currently we expect all parameters to be float values.
1068

1069
    """
1070
    try:
1071
      float(value)
1072
    except (TypeError, ValueError), err:
1073
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
1074
                                      " '%s', error: %s" % (key, value, err))
1075

    
1076

    
1077
def GetOSImage(osparams):
1078
  """Gets the OS image value from the OS parameters.
1079

1080
  @type osparams: L{dict} or NoneType
1081
  @param osparams: OS parameters or None
1082

1083
  @rtype: string or NoneType
1084
  @return:
1085
    value of OS image contained in OS parameters, or None if the OS
1086
    parameters are None or the OS parameters do not contain an OS
1087
    image
1088

1089
  """
1090
  if osparams is None:
1091
    return None
1092
  else:
1093
    return osparams.get("os-image", None)
1094

    
1095

    
1096
def PutOSImage(osparams, os_image):
1097
  """Update OS image value in the OS parameters
1098

1099
  @type osparams: L{dict}
1100
  @param osparams: OS parameters
1101

1102
  @type os_image: string
1103
  @param os_image: OS image
1104

1105
  @rtype: NoneType
1106
  @return: None
1107

1108
  """
1109
  osparams["os-image"] = os_image
1110

    
1111

    
1112
class Instance(TaggableObject):
1113
  """Config object representing an instance."""
1114
  __slots__ = [
1115
    "name",
1116
    "primary_node",
1117
    "os",
1118
    "hypervisor",
1119
    "hvparams",
1120
    "beparams",
1121
    "osparams",
1122
    "osparams_private",
1123
    "admin_state",
1124
    "nics",
1125
    "disks",
1126
    "disk_template",
1127
    "disks_active",
1128
    "network_port",
1129
    "serial_no",
1130
    ] + _TIMESTAMPS + _UUID
1131

    
1132
  def _ComputeSecondaryNodes(self):
1133
    """Compute the list of secondary nodes.
1134

1135
    This is a simple wrapper over _ComputeAllNodes.
1136

1137
    """
1138
    all_nodes = set(self._ComputeAllNodes())
1139
    all_nodes.discard(self.primary_node)
1140
    return tuple(all_nodes)
1141

    
1142
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1143
                             "List of names of secondary nodes")
1144

    
1145
  def _ComputeAllNodes(self):
1146
    """Compute the list of all nodes.
1147

1148
    Since the data is already there (in the drbd disks), keeping it as
1149
    a separate normal attribute is redundant and if not properly
1150
    synchronised can cause problems. Thus it's better to compute it
1151
    dynamically.
1152

1153
    """
1154
    def _Helper(nodes, device):
1155
      """Recursively computes nodes given a top device."""
1156
      if device.dev_type in constants.DTS_DRBD:
1157
        nodea, nodeb = device.logical_id[:2]
1158
        nodes.add(nodea)
1159
        nodes.add(nodeb)
1160
      if device.children:
1161
        for child in device.children:
1162
          _Helper(nodes, child)
1163

    
1164
    all_nodes = set()
1165
    for device in self.disks:
1166
      _Helper(all_nodes, device)
1167
    # ensure that the primary node is always the first
1168
    all_nodes.discard(self.primary_node)
1169
    return (self.primary_node, ) + tuple(all_nodes)
1170

    
1171
  all_nodes = property(_ComputeAllNodes, None, None,
1172
                       "List of names of all the nodes of the instance")
1173

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

1177
    This function figures out what logical volumes should belong on
1178
    which nodes, recursing through a device tree.
1179

1180
    @type lvmap: dict
1181
    @param lvmap: optional dictionary to receive the
1182
        'node' : ['lv', ...] data.
1183
    @type devs: list of L{Disk}
1184
    @param devs: disks to get the LV name for. If None, all disk of this
1185
        instance are used.
1186
    @type node_uuid: string
1187
    @param node_uuid: UUID of the node to get the LV names for. If None, the
1188
        primary node of this instance is used.
1189
    @return: None if lvmap arg is given, otherwise, a dictionary of
1190
        the form { 'node_uuid' : ['volume1', 'volume2', ...], ... };
1191
        volumeN is of the form "vg_name/lv_name", compatible with
1192
        GetVolumeList()
1193

1194
    """
1195
    if node_uuid is None:
1196
      node_uuid = self.primary_node
1197

    
1198
    if lvmap is None:
1199
      lvmap = {
1200
        node_uuid: [],
1201
        }
1202
      ret = lvmap
1203
    else:
1204
      if not node_uuid in lvmap:
1205
        lvmap[node_uuid] = []
1206
      ret = None
1207

    
1208
    if not devs:
1209
      devs = self.disks
1210

    
1211
    for dev in devs:
1212
      if dev.dev_type == constants.DT_PLAIN:
1213
        lvmap[node_uuid].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1214

    
1215
      elif dev.dev_type in constants.DTS_DRBD:
1216
        if dev.children:
1217
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1218
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1219

    
1220
      elif dev.children:
1221
        self.MapLVsByNode(lvmap, dev.children, node_uuid)
1222

    
1223
    return ret
1224

    
1225
  def FindDisk(self, idx):
1226
    """Find a disk given having a specified index.
1227

1228
    This is just a wrapper that does validation of the index.
1229

1230
    @type idx: int
1231
    @param idx: the disk index
1232
    @rtype: L{Disk}
1233
    @return: the corresponding disk
1234
    @raise errors.OpPrereqError: when the given index is not valid
1235

1236
    """
1237
    try:
1238
      idx = int(idx)
1239
      return self.disks[idx]
1240
    except (TypeError, ValueError), err:
1241
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1242
                                 errors.ECODE_INVAL)
1243
    except IndexError:
1244
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1245
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1246
                                 errors.ECODE_INVAL)
1247

    
1248
  def ToDict(self, _with_private=False):
1249
    """Instance-specific conversion to standard python types.
1250

1251
    This replaces the children lists of objects with lists of standard
1252
    python types.
1253

1254
    """
1255
    bo = super(Instance, self).ToDict(_with_private=_with_private)
1256

    
1257
    if _with_private:
1258
      bo["osparams_private"] = self.osparams_private.Unprivate()
1259

    
1260
    for attr in "nics", "disks":
1261
      alist = bo.get(attr, None)
1262
      if alist:
1263
        nlist = outils.ContainerToDicts(alist)
1264
      else:
1265
        nlist = []
1266
      bo[attr] = nlist
1267
    return bo
1268

    
1269
  @classmethod
1270
  def FromDict(cls, val):
1271
    """Custom function for instances.
1272

1273
    """
1274
    if "admin_state" not in val:
1275
      if val.get("admin_up", False):
1276
        val["admin_state"] = constants.ADMINST_UP
1277
      else:
1278
        val["admin_state"] = constants.ADMINST_DOWN
1279
    if "admin_up" in val:
1280
      del val["admin_up"]
1281
    obj = super(Instance, cls).FromDict(val)
1282
    obj.nics = outils.ContainerFromDicts(obj.nics, list, NIC)
1283
    obj.disks = outils.ContainerFromDicts(obj.disks, list, Disk)
1284
    return obj
1285

    
1286
  def UpgradeConfig(self):
1287
    """Fill defaults for missing configuration values.
1288

1289
    """
1290
    for nic in self.nics:
1291
      nic.UpgradeConfig()
1292
    for disk in self.disks:
1293
      disk.UpgradeConfig()
1294
    if self.hvparams:
1295
      for key in constants.HVC_GLOBALS:
1296
        try:
1297
          del self.hvparams[key]
1298
        except KeyError:
1299
          pass
1300
    if self.osparams is None:
1301
      self.osparams = {}
1302
    if self.osparams_private is None:
1303
      self.osparams_private = serializer.PrivateDict()
1304
    UpgradeBeParams(self.beparams)
1305
    if self.disks_active is None:
1306
      self.disks_active = self.admin_state == constants.ADMINST_UP
1307

    
1308

    
1309
class OS(ConfigObject):
1310
  """Config object representing an operating system.
1311

1312
  @type supported_parameters: list
1313
  @ivar supported_parameters: a list of tuples, name and description,
1314
      containing the supported parameters by this OS
1315

1316
  @type VARIANT_DELIM: string
1317
  @cvar VARIANT_DELIM: the variant delimiter
1318

1319
  """
1320
  __slots__ = [
1321
    "name",
1322
    "path",
1323
    "api_versions",
1324
    "create_script",
1325
    "export_script",
1326
    "import_script",
1327
    "rename_script",
1328
    "verify_script",
1329
    "supported_variants",
1330
    "supported_parameters",
1331
    ]
1332

    
1333
  VARIANT_DELIM = "+"
1334

    
1335
  @classmethod
1336
  def SplitNameVariant(cls, name):
1337
    """Splits the name into the proper name and variant.
1338

1339
    @param name: the OS (unprocessed) name
1340
    @rtype: list
1341
    @return: a list of two elements; if the original name didn't
1342
        contain a variant, it's returned as an empty string
1343

1344
    """
1345
    nv = name.split(cls.VARIANT_DELIM, 1)
1346
    if len(nv) == 1:
1347
      nv.append("")
1348
    return nv
1349

    
1350
  @classmethod
1351
  def GetName(cls, name):
1352
    """Returns the proper name of the os (without the variant).
1353

1354
    @param name: the OS (unprocessed) name
1355

1356
    """
1357
    return cls.SplitNameVariant(name)[0]
1358

    
1359
  @classmethod
1360
  def GetVariant(cls, name):
1361
    """Returns the variant the os (without the base name).
1362

1363
    @param name: the OS (unprocessed) name
1364

1365
    """
1366
    return cls.SplitNameVariant(name)[1]
1367

    
1368

    
1369
class ExtStorage(ConfigObject):
1370
  """Config object representing an External Storage Provider.
1371

1372
  """
1373
  __slots__ = [
1374
    "name",
1375
    "path",
1376
    "create_script",
1377
    "remove_script",
1378
    "grow_script",
1379
    "attach_script",
1380
    "detach_script",
1381
    "setinfo_script",
1382
    "verify_script",
1383
    "supported_parameters",
1384
    ]
1385

    
1386

    
1387
class NodeHvState(ConfigObject):
1388
  """Hypvervisor state on a node.
1389

1390
  @ivar mem_total: Total amount of memory
1391
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1392
    available)
1393
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1394
    rounding
1395
  @ivar mem_inst: Memory used by instances living on node
1396
  @ivar cpu_total: Total node CPU core count
1397
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1398

1399
  """
1400
  __slots__ = [
1401
    "mem_total",
1402
    "mem_node",
1403
    "mem_hv",
1404
    "mem_inst",
1405
    "cpu_total",
1406
    "cpu_node",
1407
    ] + _TIMESTAMPS
1408

    
1409

    
1410
class NodeDiskState(ConfigObject):
1411
  """Disk state on a node.
1412

1413
  """
1414
  __slots__ = [
1415
    "total",
1416
    "reserved",
1417
    "overhead",
1418
    ] + _TIMESTAMPS
1419

    
1420

    
1421
class Node(TaggableObject):
1422
  """Config object representing a node.
1423

1424
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1425
  @ivar hv_state_static: Hypervisor state overriden by user
1426
  @ivar disk_state: Disk state (e.g. free space)
1427
  @ivar disk_state_static: Disk state overriden by user
1428

1429
  """
1430
  __slots__ = [
1431
    "name",
1432
    "primary_ip",
1433
    "secondary_ip",
1434
    "serial_no",
1435
    "master_candidate",
1436
    "offline",
1437
    "drained",
1438
    "group",
1439
    "master_capable",
1440
    "vm_capable",
1441
    "ndparams",
1442
    "powered",
1443
    "hv_state",
1444
    "hv_state_static",
1445
    "disk_state",
1446
    "disk_state_static",
1447
    ] + _TIMESTAMPS + _UUID
1448

    
1449
  def UpgradeConfig(self):
1450
    """Fill defaults for missing configuration values.
1451

1452
    """
1453
    # pylint: disable=E0203
1454
    # because these are "defined" via slots, not manually
1455
    if self.master_capable is None:
1456
      self.master_capable = True
1457

    
1458
    if self.vm_capable is None:
1459
      self.vm_capable = True
1460

    
1461
    if self.ndparams is None:
1462
      self.ndparams = {}
1463
    # And remove any global parameter
1464
    for key in constants.NDC_GLOBALS:
1465
      if key in self.ndparams:
1466
        logging.warning("Ignoring %s node parameter for node %s",
1467
                        key, self.name)
1468
        del self.ndparams[key]
1469

    
1470
    if self.powered is None:
1471
      self.powered = True
1472

    
1473
  def ToDict(self, _with_private=False):
1474
    """Custom function for serializing.
1475

1476
    """
1477
    data = super(Node, self).ToDict(_with_private=_with_private)
1478

    
1479
    hv_state = data.get("hv_state", None)
1480
    if hv_state is not None:
1481
      data["hv_state"] = outils.ContainerToDicts(hv_state)
1482

    
1483
    disk_state = data.get("disk_state", None)
1484
    if disk_state is not None:
1485
      data["disk_state"] = \
1486
        dict((key, outils.ContainerToDicts(value))
1487
             for (key, value) in disk_state.items())
1488

    
1489
    return data
1490

    
1491
  @classmethod
1492
  def FromDict(cls, val):
1493
    """Custom function for deserializing.
1494

1495
    """
1496
    obj = super(Node, cls).FromDict(val)
1497

    
1498
    if obj.hv_state is not None:
1499
      obj.hv_state = \
1500
        outils.ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1501

    
1502
    if obj.disk_state is not None:
1503
      obj.disk_state = \
1504
        dict((key, outils.ContainerFromDicts(value, dict, NodeDiskState))
1505
             for (key, value) in obj.disk_state.items())
1506

    
1507
    return obj
1508

    
1509

    
1510
class NodeGroup(TaggableObject):
1511
  """Config object representing a node group."""
1512
  __slots__ = [
1513
    "name",
1514
    "members",
1515
    "ndparams",
1516
    "diskparams",
1517
    "ipolicy",
1518
    "serial_no",
1519
    "hv_state_static",
1520
    "disk_state_static",
1521
    "alloc_policy",
1522
    "networks",
1523
    ] + _TIMESTAMPS + _UUID
1524

    
1525
  def ToDict(self, _with_private=False):
1526
    """Custom function for nodegroup.
1527

1528
    This discards the members object, which gets recalculated and is only kept
1529
    in memory.
1530

1531
    """
1532
    mydict = super(NodeGroup, self).ToDict(_with_private=_with_private)
1533
    del mydict["members"]
1534
    return mydict
1535

    
1536
  @classmethod
1537
  def FromDict(cls, val):
1538
    """Custom function for nodegroup.
1539

1540
    The members slot is initialized to an empty list, upon deserialization.
1541

1542
    """
1543
    obj = super(NodeGroup, cls).FromDict(val)
1544
    obj.members = []
1545
    return obj
1546

    
1547
  def UpgradeConfig(self):
1548
    """Fill defaults for missing configuration values.
1549

1550
    """
1551
    if self.ndparams is None:
1552
      self.ndparams = {}
1553

    
1554
    if self.serial_no is None:
1555
      self.serial_no = 1
1556

    
1557
    if self.alloc_policy is None:
1558
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1559

    
1560
    # We only update mtime, and not ctime, since we would not be able
1561
    # to provide a correct value for creation time.
1562
    if self.mtime is None:
1563
      self.mtime = time.time()
1564

    
1565
    if self.diskparams is None:
1566
      self.diskparams = {}
1567
    if self.ipolicy is None:
1568
      self.ipolicy = MakeEmptyIPolicy()
1569

    
1570
    if self.networks is None:
1571
      self.networks = {}
1572

    
1573
  def FillND(self, node):
1574
    """Return filled out ndparams for L{objects.Node}
1575

1576
    @type node: L{objects.Node}
1577
    @param node: A Node object to fill
1578
    @return a copy of the node's ndparams with defaults filled
1579

1580
    """
1581
    return self.SimpleFillND(node.ndparams)
1582

    
1583
  def SimpleFillND(self, ndparams):
1584
    """Fill a given ndparams dict with defaults.
1585

1586
    @type ndparams: dict
1587
    @param ndparams: the dict to fill
1588
    @rtype: dict
1589
    @return: a copy of the passed in ndparams with missing keys filled
1590
        from the node group defaults
1591

1592
    """
1593
    return FillDict(self.ndparams, ndparams)
1594

    
1595

    
1596
class Cluster(TaggableObject):
1597
  """Config object representing the cluster."""
1598
  __slots__ = [
1599
    "serial_no",
1600
    "rsahostkeypub",
1601
    "dsahostkeypub",
1602
    "highest_used_port",
1603
    "tcpudp_port_pool",
1604
    "mac_prefix",
1605
    "volume_group_name",
1606
    "reserved_lvs",
1607
    "drbd_usermode_helper",
1608
    "default_bridge",
1609
    "default_hypervisor",
1610
    "master_node",
1611
    "master_ip",
1612
    "master_netdev",
1613
    "master_netmask",
1614
    "use_external_mip_script",
1615
    "cluster_name",
1616
    "file_storage_dir",
1617
    "shared_file_storage_dir",
1618
    "gluster_storage_dir",
1619
    "enabled_hypervisors",
1620
    "hvparams",
1621
    "ipolicy",
1622
    "os_hvp",
1623
    "beparams",
1624
    "osparams",
1625
    "osparams_private_cluster",
1626
    "nicparams",
1627
    "ndparams",
1628
    "diskparams",
1629
    "candidate_pool_size",
1630
    "modify_etc_hosts",
1631
    "modify_ssh_setup",
1632
    "maintain_node_health",
1633
    "uid_pool",
1634
    "default_iallocator",
1635
    "default_iallocator_params",
1636
    "hidden_os",
1637
    "blacklisted_os",
1638
    "primary_ip_family",
1639
    "prealloc_wipe_disks",
1640
    "hv_state_static",
1641
    "disk_state_static",
1642
    "enabled_disk_templates",
1643
    "candidate_certs",
1644
    "max_running_jobs",
1645
    "instance_communication_network",
1646
    ] + _TIMESTAMPS + _UUID
1647

    
1648
  def UpgradeConfig(self):
1649
    """Fill defaults for missing configuration values.
1650

1651
    """
1652
    # pylint: disable=E0203
1653
    # because these are "defined" via slots, not manually
1654
    if self.hvparams is None:
1655
      self.hvparams = constants.HVC_DEFAULTS
1656
    else:
1657
      for hypervisor in constants.HYPER_TYPES:
1658
        try:
1659
          existing_params = self.hvparams[hypervisor]
1660
        except KeyError:
1661
          existing_params = {}
1662
        self.hvparams[hypervisor] = FillDict(
1663
            constants.HVC_DEFAULTS[hypervisor], existing_params)
1664

    
1665
    if self.os_hvp is None:
1666
      self.os_hvp = {}
1667

    
1668
    if self.osparams is None:
1669
      self.osparams = {}
1670
    # osparams_private_cluster added in 2.12
1671
    if self.osparams_private_cluster is None:
1672
      self.osparams_private_cluster = {}
1673

    
1674
    self.ndparams = UpgradeNDParams(self.ndparams)
1675

    
1676
    self.beparams = UpgradeGroupedParams(self.beparams,
1677
                                         constants.BEC_DEFAULTS)
1678
    for beparams_group in self.beparams:
1679
      UpgradeBeParams(self.beparams[beparams_group])
1680

    
1681
    migrate_default_bridge = not self.nicparams
1682
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1683
                                          constants.NICC_DEFAULTS)
1684
    if migrate_default_bridge:
1685
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1686
        self.default_bridge
1687

    
1688
    if self.modify_etc_hosts is None:
1689
      self.modify_etc_hosts = True
1690

    
1691
    if self.modify_ssh_setup is None:
1692
      self.modify_ssh_setup = True
1693

    
1694
    # default_bridge is no longer used in 2.1. The slot is left there to
1695
    # support auto-upgrading. It can be removed once we decide to deprecate
1696
    # upgrading straight from 2.0.
1697
    if self.default_bridge is not None:
1698
      self.default_bridge = None
1699

    
1700
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1701
    # code can be removed once upgrading straight from 2.0 is deprecated.
1702
    if self.default_hypervisor is not None:
1703
      self.enabled_hypervisors = ([self.default_hypervisor] +
1704
                                  [hvname for hvname in self.enabled_hypervisors
1705
                                   if hvname != self.default_hypervisor])
1706
      self.default_hypervisor = None
1707

    
1708
    # maintain_node_health added after 2.1.1
1709
    if self.maintain_node_health is None:
1710
      self.maintain_node_health = False
1711

    
1712
    if self.uid_pool is None:
1713
      self.uid_pool = []
1714

    
1715
    if self.default_iallocator is None:
1716
      self.default_iallocator = ""
1717

    
1718
    if self.default_iallocator_params is None:
1719
      self.default_iallocator_params = {}
1720

    
1721
    # reserved_lvs added before 2.2
1722
    if self.reserved_lvs is None:
1723
      self.reserved_lvs = []
1724

    
1725
    # hidden and blacklisted operating systems added before 2.2.1
1726
    if self.hidden_os is None:
1727
      self.hidden_os = []
1728

    
1729
    if self.blacklisted_os is None:
1730
      self.blacklisted_os = []
1731

    
1732
    # primary_ip_family added before 2.3
1733
    if self.primary_ip_family is None:
1734
      self.primary_ip_family = AF_INET
1735

    
1736
    if self.master_netmask is None:
1737
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1738
      self.master_netmask = ipcls.iplen
1739

    
1740
    if self.prealloc_wipe_disks is None:
1741
      self.prealloc_wipe_disks = False
1742

    
1743
    # shared_file_storage_dir added before 2.5
1744
    if self.shared_file_storage_dir is None:
1745
      self.shared_file_storage_dir = ""
1746

    
1747
    # gluster_storage_dir added in 2.11
1748
    if self.gluster_storage_dir is None:
1749
      self.gluster_storage_dir = ""
1750

    
1751
    if self.use_external_mip_script is None:
1752
      self.use_external_mip_script = False
1753

    
1754
    if self.diskparams:
1755
      self.diskparams = UpgradeDiskParams(self.diskparams)
1756
    else:
1757
      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1758

    
1759
    # instance policy added before 2.6
1760
    if self.ipolicy is None:
1761
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1762
    else:
1763
      # we can either make sure to upgrade the ipolicy always, or only
1764
      # do it in some corner cases (e.g. missing keys); note that this
1765
      # will break any removal of keys from the ipolicy dict
1766
      wrongkeys = frozenset(self.ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
1767
      if wrongkeys:
1768
        # These keys would be silently removed by FillIPolicy()
1769
        msg = ("Cluster instance policy contains spurious keys: %s" %
1770
               utils.CommaJoin(wrongkeys))
1771
        raise errors.ConfigurationError(msg)
1772
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1773

    
1774
    if self.candidate_certs is None:
1775
      self.candidate_certs = {}
1776

    
1777
    if self.max_running_jobs is None:
1778
      self.max_running_jobs = constants.LUXID_MAXIMAL_RUNNING_JOBS_DEFAULT
1779

    
1780
    if self.instance_communication_network is None:
1781
      self.instance_communication_network = ""
1782

    
1783
  @property
1784
  def primary_hypervisor(self):
1785
    """The first hypervisor is the primary.
1786

1787
    Useful, for example, for L{Node}'s hv/disk state.
1788

1789
    """
1790
    return self.enabled_hypervisors[0]
1791

    
1792
  def ToDict(self, _with_private=False):
1793
    """Custom function for cluster.
1794

1795
    """
1796
    mydict = super(Cluster, self).ToDict(_with_private=_with_private)
1797

    
1798
    # Explicitly save private parameters.
1799
    if _with_private:
1800
      for os in mydict["osparams_private_cluster"]:
1801
        mydict["osparams_private_cluster"][os] = \
1802
          self.osparams_private_cluster[os].Unprivate()
1803

    
1804
    if self.tcpudp_port_pool is None:
1805
      tcpudp_port_pool = []
1806
    else:
1807
      tcpudp_port_pool = list(self.tcpudp_port_pool)
1808

    
1809
    mydict["tcpudp_port_pool"] = tcpudp_port_pool
1810

    
1811
    return mydict
1812

    
1813
  @classmethod
1814
  def FromDict(cls, val):
1815
    """Custom function for cluster.
1816

1817
    """
1818
    obj = super(Cluster, cls).FromDict(val)
1819

    
1820
    if obj.tcpudp_port_pool is None:
1821
      obj.tcpudp_port_pool = set()
1822
    elif not isinstance(obj.tcpudp_port_pool, set):
1823
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1824

    
1825
    return obj
1826

    
1827
  def SimpleFillDP(self, diskparams):
1828
    """Fill a given diskparams dict with cluster defaults.
1829

1830
    @param diskparams: The diskparams
1831
    @return: The defaults dict
1832

1833
    """
1834
    return FillDiskParams(self.diskparams, diskparams)
1835

    
1836
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1837
    """Get the default hypervisor parameters for the cluster.
1838

1839
    @param hypervisor: the hypervisor name
1840
    @param os_name: if specified, we'll also update the defaults for this OS
1841
    @param skip_keys: if passed, list of keys not to use
1842
    @return: the defaults dict
1843

1844
    """
1845
    if skip_keys is None:
1846
      skip_keys = []
1847

    
1848
    fill_stack = [self.hvparams.get(hypervisor, {})]
1849
    if os_name is not None:
1850
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1851
      fill_stack.append(os_hvp)
1852

    
1853
    ret_dict = {}
1854
    for o_dict in fill_stack:
1855
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1856

    
1857
    return ret_dict
1858

    
1859
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1860
    """Fill a given hvparams dict with cluster defaults.
1861

1862
    @type hv_name: string
1863
    @param hv_name: the hypervisor to use
1864
    @type os_name: string
1865
    @param os_name: the OS to use for overriding the hypervisor defaults
1866
    @type skip_globals: boolean
1867
    @param skip_globals: if True, the global hypervisor parameters will
1868
        not be filled
1869
    @rtype: dict
1870
    @return: a copy of the given hvparams with missing keys filled from
1871
        the cluster defaults
1872

1873
    """
1874
    if skip_globals:
1875
      skip_keys = constants.HVC_GLOBALS
1876
    else:
1877
      skip_keys = []
1878

    
1879
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1880
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1881

    
1882
  def FillHV(self, instance, skip_globals=False):
1883
    """Fill an instance's hvparams dict with cluster defaults.
1884

1885
    @type instance: L{objects.Instance}
1886
    @param instance: the instance parameter to fill
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 instance's hvparams with missing keys filled from
1892
        the cluster defaults
1893

1894
    """
1895
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1896
                             instance.hvparams, skip_globals)
1897

    
1898
  def SimpleFillBE(self, beparams):
1899
    """Fill a given beparams dict with cluster defaults.
1900

1901
    @type beparams: dict
1902
    @param beparams: the dict to fill
1903
    @rtype: dict
1904
    @return: a copy of the passed in beparams with missing keys filled
1905
        from the cluster defaults
1906

1907
    """
1908
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1909

    
1910
  def FillBE(self, instance):
1911
    """Fill an instance's beparams dict with cluster defaults.
1912

1913
    @type instance: L{objects.Instance}
1914
    @param instance: the instance parameter to fill
1915
    @rtype: dict
1916
    @return: a copy of the instance's beparams with missing keys filled from
1917
        the cluster defaults
1918

1919
    """
1920
    return self.SimpleFillBE(instance.beparams)
1921

    
1922
  def SimpleFillNIC(self, nicparams):
1923
    """Fill a given nicparams dict with cluster defaults.
1924

1925
    @type nicparams: dict
1926
    @param nicparams: the dict to fill
1927
    @rtype: dict
1928
    @return: a copy of the passed in nicparams with missing keys filled
1929
        from the cluster defaults
1930

1931
    """
1932
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1933

    
1934
  def SimpleFillOS(self, os_name,
1935
                    os_params_public,
1936
                    os_params_private=None,
1937
                    os_params_secret=None):
1938
    """Fill an instance's osparams dict with cluster defaults.
1939

1940
    @type os_name: string
1941
    @param os_name: the OS name to use
1942
    @type os_params_public: dict
1943
    @param os_params_public: the dict to fill with default values
1944
    @type os_params_private: dict
1945
    @param os_params_private: the dict with private fields to fill
1946
                              with default values. Not passing this field
1947
                              results in no private fields being added to the
1948
                              return value. Private fields will be wrapped in
1949
                              L{Private} objects.
1950
    @type os_params_secret: dict
1951
    @param os_params_secret: the dict with secret fields to fill
1952
                             with default values. Not passing this field
1953
                             results in no secret fields being added to the
1954
                             return value. Private fields will be wrapped in
1955
                             L{Private} objects.
1956
    @rtype: dict
1957
    @return: a copy of the instance's osparams with missing keys filled from
1958
        the cluster defaults. Private and secret parameters are not included
1959
        unless the respective optional parameters are supplied.
1960

1961
    """
1962
    if os_name is None:
1963
      name_only = None
1964
    else:
1965
      name_only = OS.GetName(os_name)
1966

    
1967
    defaults_base_public = self.osparams.get(name_only, {})
1968
    defaults_public = FillDict(defaults_base_public,
1969
                               self.osparams.get(os_name, {}))
1970
    params_public = FillDict(defaults_public, os_params_public)
1971

    
1972
    if os_params_private is not None:
1973
      defaults_base_private = self.osparams_private_cluster.get(name_only, {})
1974
      defaults_private = FillDict(defaults_base_private,
1975
                                  self.osparams_private_cluster.get(os_name,
1976
                                                                    {}))
1977
      params_private = FillDict(defaults_private, os_params_private)
1978
    else:
1979
      params_private = {}
1980

    
1981
    if os_params_secret is not None:
1982
      # There can't be default secret settings, so there's nothing to be done.
1983
      params_secret = os_params_secret
1984
    else:
1985
      params_secret = {}
1986

    
1987
    # Enforce that the set of keys be distinct:
1988
    duplicate_keys = utils.GetRepeatedKeys(params_public,
1989
                                           params_private,
1990
                                           params_secret)
1991
    if not duplicate_keys:
1992

    
1993
      # Actually update them:
1994
      params_public.update(params_private)
1995
      params_public.update(params_secret)
1996

    
1997
      return params_public
1998

    
1999
    else:
2000

    
2001
      def formatter(keys):
2002
        return utils.CommaJoin(sorted(map(repr, keys))) if keys else "(none)"
2003

    
2004
      #Lose the values.
2005
      params_public = set(params_public)
2006
      params_private = set(params_private)
2007
      params_secret = set(params_secret)
2008

    
2009
      msg = """Cannot assign multiple values to OS parameters.
2010

2011
      Conflicting OS parameters that would have been set by this operation:
2012
      - at public visibility:  {public}
2013
      - at private visibility: {private}
2014
      - at secret visibility:  {secret}
2015
      """.format(dupes=formatter(duplicate_keys),
2016
                 public=formatter(params_public & duplicate_keys),
2017
                 private=formatter(params_private & duplicate_keys),
2018
                 secret=formatter(params_secret & duplicate_keys))
2019
      raise errors.OpPrereqError(msg)
2020

    
2021
  @staticmethod
2022
  def SimpleFillHvState(hv_state):
2023
    """Fill an hv_state sub dict with cluster defaults.
2024

2025
    """
2026
    return FillDict(constants.HVST_DEFAULTS, hv_state)
2027

    
2028
  @staticmethod
2029
  def SimpleFillDiskState(disk_state):
2030
    """Fill an disk_state sub dict with cluster defaults.
2031

2032
    """
2033
    return FillDict(constants.DS_DEFAULTS, disk_state)
2034

    
2035
  def FillND(self, node, nodegroup):
2036
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
2037

2038
    @type node: L{objects.Node}
2039
    @param node: A Node object to fill
2040
    @type nodegroup: L{objects.NodeGroup}
2041
    @param nodegroup: A Node object to fill
2042
    @return a copy of the node's ndparams with defaults filled
2043

2044
    """
2045
    return self.SimpleFillND(nodegroup.FillND(node))
2046

    
2047
  def FillNDGroup(self, nodegroup):
2048
    """Return filled out ndparams for just L{objects.NodeGroup}
2049

2050
    @type nodegroup: L{objects.NodeGroup}
2051
    @param nodegroup: A Node object to fill
2052
    @return a copy of the node group's ndparams with defaults filled
2053

2054
    """
2055
    return self.SimpleFillND(nodegroup.SimpleFillND({}))
2056

    
2057
  def SimpleFillND(self, ndparams):
2058
    """Fill a given ndparams dict with defaults.
2059

2060
    @type ndparams: dict
2061
    @param ndparams: the dict to fill
2062
    @rtype: dict
2063
    @return: a copy of the passed in ndparams with missing keys filled
2064
        from the cluster defaults
2065

2066
    """
2067
    return FillDict(self.ndparams, ndparams)
2068

    
2069
  def SimpleFillIPolicy(self, ipolicy):
2070
    """ Fill instance policy dict with defaults.
2071

2072
    @type ipolicy: dict
2073
    @param ipolicy: the dict to fill
2074
    @rtype: dict
2075
    @return: a copy of passed ipolicy with missing keys filled from
2076
      the cluster defaults
2077

2078
    """
2079
    return FillIPolicy(self.ipolicy, ipolicy)
2080

    
2081
  def IsDiskTemplateEnabled(self, disk_template):
2082
    """Checks if a particular disk template is enabled.
2083

2084
    """
2085
    return utils.storage.IsDiskTemplateEnabled(
2086
        disk_template, self.enabled_disk_templates)
2087

    
2088
  def IsFileStorageEnabled(self):
2089
    """Checks if file storage is enabled.
2090

2091
    """
2092
    return utils.storage.IsFileStorageEnabled(self.enabled_disk_templates)
2093

    
2094
  def IsSharedFileStorageEnabled(self):
2095
    """Checks if shared file storage is enabled.
2096

2097
    """
2098
    return utils.storage.IsSharedFileStorageEnabled(
2099
        self.enabled_disk_templates)
2100

    
2101

    
2102
class BlockDevStatus(ConfigObject):
2103
  """Config object representing the status of a block device."""
2104
  __slots__ = [
2105
    "dev_path",
2106
    "major",
2107
    "minor",
2108
    "sync_percent",
2109
    "estimated_time",
2110
    "is_degraded",
2111
    "ldisk_status",
2112
    ]
2113

    
2114

    
2115
class ImportExportStatus(ConfigObject):
2116
  """Config object representing the status of an import or export."""
2117
  __slots__ = [
2118
    "recent_output",
2119
    "listen_port",
2120
    "connected",
2121
    "progress_mbytes",
2122
    "progress_throughput",
2123
    "progress_eta",
2124
    "progress_percent",
2125
    "exit_status",
2126
    "error_message",
2127
    ] + _TIMESTAMPS
2128

    
2129

    
2130
class ImportExportOptions(ConfigObject):
2131
  """Options for import/export daemon
2132

2133
  @ivar key_name: X509 key name (None for cluster certificate)
2134
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
2135
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
2136
  @ivar magic: Used to ensure the connection goes to the right disk
2137
  @ivar ipv6: Whether to use IPv6
2138
  @ivar connect_timeout: Number of seconds for establishing connection
2139

2140
  """
2141
  __slots__ = [
2142
    "key_name",
2143
    "ca_pem",
2144
    "compress",
2145
    "magic",
2146
    "ipv6",
2147
    "connect_timeout",
2148
    ]
2149

    
2150

    
2151
class ConfdRequest(ConfigObject):
2152
  """Object holding a confd request.
2153

2154
  @ivar protocol: confd protocol version
2155
  @ivar type: confd query type
2156
  @ivar query: query request
2157
  @ivar rsalt: requested reply salt
2158

2159
  """
2160
  __slots__ = [
2161
    "protocol",
2162
    "type",
2163
    "query",
2164
    "rsalt",
2165
    ]
2166

    
2167

    
2168
class ConfdReply(ConfigObject):
2169
  """Object holding a confd reply.
2170

2171
  @ivar protocol: confd protocol version
2172
  @ivar status: reply status code (ok, error)
2173
  @ivar answer: confd query reply
2174
  @ivar serial: configuration serial number
2175

2176
  """
2177
  __slots__ = [
2178
    "protocol",
2179
    "status",
2180
    "answer",
2181
    "serial",
2182
    ]
2183

    
2184

    
2185
class QueryFieldDefinition(ConfigObject):
2186
  """Object holding a query field definition.
2187

2188
  @ivar name: Field name
2189
  @ivar title: Human-readable title
2190
  @ivar kind: Field type
2191
  @ivar doc: Human-readable description
2192

2193
  """
2194
  __slots__ = [
2195
    "name",
2196
    "title",
2197
    "kind",
2198
    "doc",
2199
    ]
2200

    
2201

    
2202
class _QueryResponseBase(ConfigObject):
2203
  __slots__ = [
2204
    "fields",
2205
    ]
2206

    
2207
  def ToDict(self, _with_private=False):
2208
    """Custom function for serializing.
2209

2210
    """
2211
    mydict = super(_QueryResponseBase, self).ToDict()
2212
    mydict["fields"] = outils.ContainerToDicts(mydict["fields"])
2213
    return mydict
2214

    
2215
  @classmethod
2216
  def FromDict(cls, val):
2217
    """Custom function for de-serializing.
2218

2219
    """
2220
    obj = super(_QueryResponseBase, cls).FromDict(val)
2221
    obj.fields = \
2222
      outils.ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
2223
    return obj
2224

    
2225

    
2226
class QueryResponse(_QueryResponseBase):
2227
  """Object holding the response to a query.
2228

2229
  @ivar fields: List of L{QueryFieldDefinition} objects
2230
  @ivar data: Requested data
2231

2232
  """
2233
  __slots__ = [
2234
    "data",
2235
    ]
2236

    
2237

    
2238
class QueryFieldsRequest(ConfigObject):
2239
  """Object holding a request for querying available fields.
2240

2241
  """
2242
  __slots__ = [
2243
    "what",
2244
    "fields",
2245
    ]
2246

    
2247

    
2248
class QueryFieldsResponse(_QueryResponseBase):
2249
  """Object holding the response to a query for fields.
2250

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

2253
  """
2254
  __slots__ = []
2255

    
2256

    
2257
class MigrationStatus(ConfigObject):
2258
  """Object holding the status of a migration.
2259

2260
  """
2261
  __slots__ = [
2262
    "status",
2263
    "transferred_ram",
2264
    "total_ram",
2265
    ]
2266

    
2267

    
2268
class InstanceConsole(ConfigObject):
2269
  """Object describing how to access the console of an instance.
2270

2271
  """
2272
  __slots__ = [
2273
    "instance",
2274
    "kind",
2275
    "message",
2276
    "host",
2277
    "port",
2278
    "user",
2279
    "command",
2280
    "display",
2281
    ]
2282

    
2283
  def Validate(self):
2284
    """Validates contents of this object.
2285

2286
    """
2287
    assert self.kind in constants.CONS_ALL, "Unknown console type"
2288
    assert self.instance, "Missing instance name"
2289
    assert self.message or self.kind in [constants.CONS_SSH,
2290
                                         constants.CONS_SPICE,
2291
                                         constants.CONS_VNC]
2292
    assert self.host or self.kind == constants.CONS_MESSAGE
2293
    assert self.port or self.kind in [constants.CONS_MESSAGE,
2294
                                      constants.CONS_SSH]
2295
    assert self.user or self.kind in [constants.CONS_MESSAGE,
2296
                                      constants.CONS_SPICE,
2297
                                      constants.CONS_VNC]
2298
    assert self.command or self.kind in [constants.CONS_MESSAGE,
2299
                                         constants.CONS_SPICE,
2300
                                         constants.CONS_VNC]
2301
    assert self.display or self.kind in [constants.CONS_MESSAGE,
2302
                                         constants.CONS_SPICE,
2303
                                         constants.CONS_SSH]
2304

    
2305

    
2306
class Network(TaggableObject):
2307
  """Object representing a network definition for ganeti.
2308

2309
  """
2310
  __slots__ = [
2311
    "name",
2312
    "serial_no",
2313
    "mac_prefix",
2314
    "network",
2315
    "network6",
2316
    "gateway",
2317
    "gateway6",
2318
    "reservations",
2319
    "ext_reservations",
2320
    ] + _TIMESTAMPS + _UUID
2321

    
2322
  def HooksDict(self, prefix=""):
2323
    """Export a dictionary used by hooks with a network's information.
2324

2325
    @type prefix: String
2326
    @param prefix: Prefix to prepend to the dict entries
2327

2328
    """
2329
    result = {
2330
      "%sNETWORK_NAME" % prefix: self.name,
2331
      "%sNETWORK_UUID" % prefix: self.uuid,
2332
      "%sNETWORK_TAGS" % prefix: " ".join(self.GetTags()),
2333
    }
2334
    if self.network:
2335
      result["%sNETWORK_SUBNET" % prefix] = self.network
2336
    if self.gateway:
2337
      result["%sNETWORK_GATEWAY" % prefix] = self.gateway
2338
    if self.network6:
2339
      result["%sNETWORK_SUBNET6" % prefix] = self.network6
2340
    if self.gateway6:
2341
      result["%sNETWORK_GATEWAY6" % prefix] = self.gateway6
2342
    if self.mac_prefix:
2343
      result["%sNETWORK_MAC_PREFIX" % prefix] = self.mac_prefix
2344

    
2345
    return result
2346

    
2347
  @classmethod
2348
  def FromDict(cls, val):
2349
    """Custom function for networks.
2350

2351
    Remove deprecated network_type and family.
2352

2353
    """
2354
    if "network_type" in val:
2355
      del val["network_type"]
2356
    if "family" in val:
2357
      del val["family"]
2358
    obj = super(Network, cls).FromDict(val)
2359
    return obj
2360

    
2361

    
2362
# need to inherit object in order to use super()
2363
class SerializableConfigParser(ConfigParser.SafeConfigParser, object):
2364
  """Simple wrapper over ConfigParse that allows serialization.
2365

2366
  This class is basically ConfigParser.SafeConfigParser with two
2367
  additional methods that allow it to serialize/unserialize to/from a
2368
  buffer.
2369

2370
  """
2371
  def Dumps(self):
2372
    """Dump this instance and return the string representation."""
2373
    buf = StringIO()
2374
    self.write(buf)
2375
    return buf.getvalue()
2376

    
2377
  @classmethod
2378
  def Loads(cls, data):
2379
    """Load data from a string."""
2380
    buf = StringIO(data)
2381
    cfp = cls()
2382
    cfp.readfp(buf)
2383
    return cfp
2384

    
2385
  def get(self, section, option, **kwargs):
2386
    value = None
2387
    try:
2388
      value = super(SerializableConfigParser, self).get(section, option,
2389
                                                        **kwargs)
2390
      if value.lower() == constants.VALUE_NONE:
2391
        value = None
2392
    except ConfigParser.NoOptionError:
2393
      r = re.compile(r"(disk|nic)\d+_name|nic\d+_(network|vlan)")
2394
      match = r.match(option)
2395
      if match:
2396
        pass
2397
      else:
2398
        raise
2399

    
2400
    return value
2401

    
2402

    
2403
class LvmPvInfo(ConfigObject):
2404
  """Information about an LVM physical volume (PV).
2405

2406
  @type name: string
2407
  @ivar name: name of the PV
2408
  @type vg_name: string
2409
  @ivar vg_name: name of the volume group containing the PV
2410
  @type size: float
2411
  @ivar size: size of the PV in MiB
2412
  @type free: float
2413
  @ivar free: free space in the PV, in MiB
2414
  @type attributes: string
2415
  @ivar attributes: PV attributes
2416
  @type lv_list: list of strings
2417
  @ivar lv_list: names of the LVs hosted on the PV
2418
  """
2419
  __slots__ = [
2420
    "name",
2421
    "vg_name",
2422
    "size",
2423
    "free",
2424
    "attributes",
2425
    "lv_list"
2426
    ]
2427

    
2428
  def IsEmpty(self):
2429
    """Is this PV empty?
2430

2431
    """
2432
    return self.size <= (self.free + 1)
2433

    
2434
  def IsAllocatable(self):
2435
    """Is this PV allocatable?
2436

2437
    """
2438
    return ("a" in self.attributes)