Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 938adc87

History | View | Annotate | Download (58.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 time
42
from cStringIO import StringIO
43

    
44
from ganeti import errors
45
from ganeti import constants
46
from ganeti import netutils
47
from ganeti import objectutils
48
from ganeti import utils
49

    
50
from socket import AF_INET
51

    
52

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

    
56
_TIMESTAMPS = ["ctime", "mtime"]
57
_UUID = ["uuid"]
58

    
59

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

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

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

    
83

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

87
  """
88
  assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS
89
  ret_dict = {}
90
  for key in constants.IPOLICY_ISPECS:
91
    ret_dict[key] = FillDict(default_ipolicy[key],
92
                             custom_ipolicy.get(key, {}),
93
                             skip_keys=skip_keys)
94
  # list items
95
  for key in [constants.IPOLICY_DTS]:
96
    ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key]))
97
  # other items which we know we can directly copy (immutables)
98
  for key in constants.IPOLICY_PARAMETERS:
99
    ret_dict[key] = custom_ipolicy.get(key, default_ipolicy[key])
100

    
101
  return ret_dict
102

    
103

    
104
def FillDiskParams(default_dparams, custom_dparams, skip_keys=None):
105
  """Fills the disk parameter defaults.
106

107
  @see: L{FillDict} for parameters and return value
108

109
  """
110
  assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES
111

    
112
  return dict((dt, FillDict(default_dparams[dt], custom_dparams.get(dt, {}),
113
                             skip_keys=skip_keys))
114
              for dt in constants.DISK_TEMPLATES)
115

    
116

    
117
def UpgradeGroupedParams(target, defaults):
118
  """Update all groups for the target parameter.
119

120
  @type target: dict of dicts
121
  @param target: {group: {parameter: value}}
122
  @type defaults: dict
123
  @param defaults: default parameter values
124

125
  """
126
  if target is None:
127
    target = {constants.PP_DEFAULT: defaults}
128
  else:
129
    for group in target:
130
      target[group] = FillDict(defaults, target[group])
131
  return target
132

    
133

    
134
def UpgradeBeParams(target):
135
  """Update the be parameters dict to the new format.
136

137
  @type target: dict
138
  @param target: "be" parameters dict
139

140
  """
141
  if constants.BE_MEMORY in target:
142
    memory = target[constants.BE_MEMORY]
143
    target[constants.BE_MAXMEM] = memory
144
    target[constants.BE_MINMEM] = memory
145
    del target[constants.BE_MEMORY]
146

    
147

    
148
def UpgradeDiskParams(diskparams):
149
  """Upgrade the disk parameters.
150

151
  @type diskparams: dict
152
  @param diskparams: disk parameters to upgrade
153
  @rtype: dict
154
  @return: the upgraded disk parameters dict
155

156
  """
157
  if not diskparams:
158
    result = {}
159
  else:
160
    result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams)
161

    
162
  return result
163

    
164

    
165
def UpgradeNDParams(ndparams):
166
  """Upgrade ndparams structure.
167

168
  @type ndparams: dict
169
  @param ndparams: disk parameters to upgrade
170
  @rtype: dict
171
  @return: the upgraded node parameters dict
172

173
  """
174
  if ndparams is None:
175
    ndparams = {}
176

    
177
  if (constants.ND_OOB_PROGRAM in ndparams and
178
      ndparams[constants.ND_OOB_PROGRAM] is None):
179
    # will be reset by the line below
180
    del ndparams[constants.ND_OOB_PROGRAM]
181
  return FillDict(constants.NDC_DEFAULTS, ndparams)
182

    
183

    
184
def MakeEmptyIPolicy():
185
  """Create empty IPolicy dictionary.
186

187
  """
188
  return dict([
189
    (constants.ISPECS_MIN, {}),
190
    (constants.ISPECS_MAX, {}),
191
    (constants.ISPECS_STD, {}),
192
    ])
193

    
194

    
195
class ConfigObject(objectutils.ValidatedSlots):
196
  """A generic config object.
197

198
  It has the following properties:
199

200
    - provides somewhat safe recursive unpickling and pickling for its classes
201
    - unset attributes which are defined in slots are always returned
202
      as None instead of raising an error
203

204
  Classes derived from this must always declare __slots__ (we use many
205
  config objects and the memory reduction is useful)
206

207
  """
208
  __slots__ = []
209

    
210
  def __getattr__(self, name):
211
    if name not in self.GetAllSlots():
212
      raise AttributeError("Invalid object attribute %s.%s" %
213
                           (type(self).__name__, name))
214
    return None
215

    
216
  def __setstate__(self, state):
217
    slots = self.GetAllSlots()
218
    for name in state:
219
      if name in slots:
220
        setattr(self, name, state[name])
221

    
222
  def Validate(self):
223
    """Validates the slots.
224

225
    """
226

    
227
  def ToDict(self):
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
    """
237
    result = {}
238
    for name in self.GetAllSlots():
239
      value = getattr(self, name, None)
240
      if value is not None:
241
        result[name] = value
242
    return result
243

    
244
  __getstate__ = ToDict
245

    
246
  @classmethod
247
  def FromDict(cls, val):
248
    """Create an object from a dictionary.
249

250
    This generic routine takes a dict, instantiates a new instance of
251
    the given class, and sets attributes based on the dict content.
252

253
    As for `ToDict`, this does not work if the class has children
254
    who are ConfigObjects themselves (e.g. the nics list in an
255
    Instance), in which case the object should subclass the function
256
    and alter the objects.
257

258
    """
259
    if not isinstance(val, dict):
260
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
261
                                      " expected dict, got %s" % type(val))
262
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
263
    obj = cls(**val_str) # pylint: disable=W0142
264
    return obj
265

    
266
  @staticmethod
267
  def _ContainerToDicts(container):
268
    """Convert the elements of a container to standard python types.
269

270
    This method converts a container with elements derived from
271
    ConfigData to standard python types. If the container is a dict,
272
    we don't touch the keys, only the values.
273

274
    """
275
    if isinstance(container, dict):
276
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
277
    elif isinstance(container, (list, tuple, set, frozenset)):
278
      ret = [elem.ToDict() for elem in container]
279
    else:
280
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
281
                      type(container))
282
    return ret
283

    
284
  @staticmethod
285
  def _ContainerFromDicts(source, c_type, e_type):
286
    """Convert a container from standard python types.
287

288
    This method converts a container with standard python types to
289
    ConfigData objects. If the container is a dict, we don't touch the
290
    keys, only the values.
291

292
    """
293
    if not isinstance(c_type, type):
294
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
295
                      " not a type" % type(c_type))
296
    if source is None:
297
      source = c_type()
298
    if c_type is dict:
299
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
300
    elif c_type in (list, tuple, set, frozenset):
301
      ret = c_type([e_type.FromDict(elem) for elem in source])
302
    else:
303
      raise TypeError("Invalid container type %s passed to"
304
                      " _ContainerFromDicts" % c_type)
305
    return ret
306

    
307
  def Copy(self):
308
    """Makes a deep copy of the current object and its children.
309

310
    """
311
    dict_form = self.ToDict()
312
    clone_obj = self.__class__.FromDict(dict_form)
313
    return clone_obj
314

    
315
  def __repr__(self):
316
    """Implement __repr__ for ConfigObjects."""
317
    return repr(self.ToDict())
318

    
319
  def UpgradeConfig(self):
320
    """Fill defaults for missing configuration values.
321

322
    This method will be called at configuration load time, and its
323
    implementation will be object dependent.
324

325
    """
326
    pass
327

    
328

    
329
class TaggableObject(ConfigObject):
330
  """An generic class supporting tags.
331

332
  """
333
  __slots__ = ["tags"]
334
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
335

    
336
  @classmethod
337
  def ValidateTag(cls, tag):
338
    """Check if a tag is valid.
339

340
    If the tag is invalid, an errors.TagError will be raised. The
341
    function has no return value.
342

343
    """
344
    if not isinstance(tag, basestring):
345
      raise errors.TagError("Invalid tag type (not a string)")
346
    if len(tag) > constants.MAX_TAG_LEN:
347
      raise errors.TagError("Tag too long (>%d characters)" %
348
                            constants.MAX_TAG_LEN)
349
    if not tag:
350
      raise errors.TagError("Tags cannot be empty")
351
    if not cls.VALID_TAG_RE.match(tag):
352
      raise errors.TagError("Tag contains invalid characters")
353

    
354
  def GetTags(self):
355
    """Return the tags list.
356

357
    """
358
    tags = getattr(self, "tags", None)
359
    if tags is None:
360
      tags = self.tags = set()
361
    return tags
362

    
363
  def AddTag(self, tag):
364
    """Add a new tag.
365

366
    """
367
    self.ValidateTag(tag)
368
    tags = self.GetTags()
369
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
370
      raise errors.TagError("Too many tags")
371
    self.GetTags().add(tag)
372

    
373
  def RemoveTag(self, tag):
374
    """Remove a tag.
375

376
    """
377
    self.ValidateTag(tag)
378
    tags = self.GetTags()
379
    try:
380
      tags.remove(tag)
381
    except KeyError:
382
      raise errors.TagError("Tag not found")
383

    
384
  def ToDict(self):
385
    """Taggable-object-specific conversion to standard python types.
386

387
    This replaces the tags set with a list.
388

389
    """
390
    bo = super(TaggableObject, self).ToDict()
391

    
392
    tags = bo.get("tags", None)
393
    if isinstance(tags, set):
394
      bo["tags"] = list(tags)
395
    return bo
396

    
397
  @classmethod
398
  def FromDict(cls, val):
399
    """Custom function for instances.
400

401
    """
402
    obj = super(TaggableObject, cls).FromDict(val)
403
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
404
      obj.tags = set(obj.tags)
405
    return obj
406

    
407

    
408
class MasterNetworkParameters(ConfigObject):
409
  """Network configuration parameters for the master
410

411
  @ivar name: master name
412
  @ivar ip: master IP
413
  @ivar netmask: master netmask
414
  @ivar netdev: master network device
415
  @ivar ip_family: master IP family
416

417
  """
418
  __slots__ = [
419
    "name",
420
    "ip",
421
    "netmask",
422
    "netdev",
423
    "ip_family",
424
    ]
425

    
426

    
427
class ConfigData(ConfigObject):
428
  """Top-level config object."""
429
  __slots__ = [
430
    "version",
431
    "cluster",
432
    "nodes",
433
    "nodegroups",
434
    "instances",
435
    "networks",
436
    "serial_no",
437
    ] + _TIMESTAMPS
438

    
439
  def ToDict(self):
440
    """Custom function for top-level config data.
441

442
    This just replaces the list of instances, nodes and the cluster
443
    with standard python types.
444

445
    """
446
    mydict = super(ConfigData, self).ToDict()
447
    mydict["cluster"] = mydict["cluster"].ToDict()
448
    for key in "nodes", "instances", "nodegroups", "networks":
449
      mydict[key] = self._ContainerToDicts(mydict[key])
450

    
451
    return mydict
452

    
453
  @classmethod
454
  def FromDict(cls, val):
455
    """Custom function for top-level config data
456

457
    """
458
    obj = super(ConfigData, cls).FromDict(val)
459
    obj.cluster = Cluster.FromDict(obj.cluster)
460
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
461
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
462
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
463
    obj.networks = cls._ContainerFromDicts(obj.networks, dict, Network)
464
    return obj
465

    
466
  def HasAnyDiskOfType(self, dev_type):
467
    """Check if in there is at disk of the given type in the configuration.
468

469
    @type dev_type: L{constants.LDS_BLOCK}
470
    @param dev_type: the type to look for
471
    @rtype: boolean
472
    @return: boolean indicating if a disk of the given type was found or not
473

474
    """
475
    for instance in self.instances.values():
476
      for disk in instance.disks:
477
        if disk.IsBasedOnDiskType(dev_type):
478
          return True
479
    return False
480

    
481
  def UpgradeConfig(self):
482
    """Fill defaults for missing configuration values.
483

484
    """
485
    self.cluster.UpgradeConfig()
486
    for node in self.nodes.values():
487
      node.UpgradeConfig()
488
    for instance in self.instances.values():
489
      instance.UpgradeConfig()
490
    if self.nodegroups is None:
491
      self.nodegroups = {}
492
    for nodegroup in self.nodegroups.values():
493
      nodegroup.UpgradeConfig()
494
    if self.cluster.drbd_usermode_helper is None:
495
      # To decide if we set an helper let's check if at least one instance has
496
      # a DRBD disk. This does not cover all the possible scenarios but it
497
      # gives a good approximation.
498
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
499
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
500
    if self.networks is None:
501
      self.networks = {}
502

    
503

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

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

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

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

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

    
527

    
528
class Disk(ConfigObject):
529
  """Config object representing a block device."""
530
  __slots__ = ["dev_type", "logical_id", "physical_id",
531
               "children", "iv_name", "size", "mode", "params"]
532

    
533
  def CreateOnSecondary(self):
534
    """Test if this device needs to be created on a secondary node."""
535
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
536

    
537
  def AssembleOnSecondary(self):
538
    """Test if this device needs to be assembled on a secondary node."""
539
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
540

    
541
  def OpenOnSecondary(self):
542
    """Test if this device needs to be opened on a secondary node."""
543
    return self.dev_type in (constants.LD_LV,)
544

    
545
  def StaticDevPath(self):
546
    """Return the device path if this device type has a static one.
547

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

552
    @warning: The path returned is not a normalized pathname; callers
553
        should check that it is a valid path.
554

555
    """
556
    if self.dev_type == constants.LD_LV:
557
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
558
    elif self.dev_type == constants.LD_BLOCKDEV:
559
      return self.logical_id[1]
560
    elif self.dev_type == constants.LD_RBD:
561
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
562
    return None
563

    
564
  def ChildrenNeeded(self):
565
    """Compute the needed number of children for activation.
566

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

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

575
    """
576
    if self.dev_type == constants.LD_DRBD8:
577
      return 0
578
    return -1
579

    
580
  def IsBasedOnDiskType(self, dev_type):
581
    """Check if the disk or its children are based on the given type.
582

583
    @type dev_type: L{constants.LDS_BLOCK}
584
    @param dev_type: the type to look for
585
    @rtype: boolean
586
    @return: boolean indicating if a device of the given type was found or not
587

588
    """
589
    if self.children:
590
      for child in self.children:
591
        if child.IsBasedOnDiskType(dev_type):
592
          return True
593
    return self.dev_type == dev_type
594

    
595
  def GetNodes(self, node):
596
    """This function returns the nodes this device lives on.
597

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

603
    """
604
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
605
                         constants.LD_BLOCKDEV, constants.LD_RBD,
606
                         constants.LD_EXT]:
607
      result = [node]
608
    elif self.dev_type in constants.LDS_DRBD:
609
      result = [self.logical_id[0], self.logical_id[1]]
610
      if node not in result:
611
        raise errors.ConfigurationError("DRBD device passed unknown node")
612
    else:
613
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
614
    return result
615

    
616
  def ComputeNodeTree(self, parent_node):
617
    """Compute the node/disk tree for this disk and its children.
618

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

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

    
651
  def ComputeGrowth(self, amount):
652
    """Compute the per-VG growth requirements.
653

654
    This only works for VG-based disks.
655

656
    @type amount: integer
657
    @param amount: the desired increase in (user-visible) disk space
658
    @rtype: dict
659
    @return: a dictionary of volume-groups and the required size
660

661
    """
662
    if self.dev_type == constants.LD_LV:
663
      return {self.logical_id[0]: amount}
664
    elif self.dev_type == constants.LD_DRBD8:
665
      if self.children:
666
        return self.children[0].ComputeGrowth(amount)
667
      else:
668
        return {}
669
    else:
670
      # Other disk types do not require VG space
671
      return {}
672

    
673
  def RecordGrow(self, amount):
674
    """Update the size of this disk after growth.
675

676
    This method recurses over the disks's children and updates their
677
    size correspondigly. The method needs to be kept in sync with the
678
    actual algorithms from bdev.
679

680
    """
681
    if self.dev_type in (constants.LD_LV, constants.LD_FILE,
682
                         constants.LD_RBD, constants.LD_EXT):
683
      self.size += amount
684
    elif self.dev_type == constants.LD_DRBD8:
685
      if self.children:
686
        self.children[0].RecordGrow(amount)
687
      self.size += amount
688
    else:
689
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
690
                                   " disk type %s" % self.dev_type)
691

    
692
  def Update(self, size=None, mode=None):
693
    """Apply changes to size and mode.
694

695
    """
696
    if self.dev_type == constants.LD_DRBD8:
697
      if self.children:
698
        self.children[0].Update(size=size, mode=mode)
699
    else:
700
      assert not self.children
701

    
702
    if size is not None:
703
      self.size = size
704
    if mode is not None:
705
      self.mode = mode
706

    
707
  def UnsetSize(self):
708
    """Sets recursively the size to zero for the disk and its children.
709

710
    """
711
    if self.children:
712
      for child in self.children:
713
        child.UnsetSize()
714
    self.size = 0
715

    
716
  def SetPhysicalID(self, target_node, nodes_ip):
717
    """Convert the logical ID to the physical ID.
718

719
    This is used only for drbd, which needs ip/port configuration.
720

721
    The routine descends down and updates its children also, because
722
    this helps when the only the top device is passed to the remote
723
    node.
724

725
    Arguments:
726
      - target_node: the node we wish to configure for
727
      - nodes_ip: a mapping of node name to ip
728

729
    The target_node must exist in in nodes_ip, and must be one of the
730
    nodes in the logical ID for each of the DRBD devices encountered
731
    in the disk tree.
732

733
    """
734
    if self.children:
735
      for child in self.children:
736
        child.SetPhysicalID(target_node, nodes_ip)
737

    
738
    if self.logical_id is None and self.physical_id is not None:
739
      return
740
    if self.dev_type in constants.LDS_DRBD:
741
      pnode, snode, port, pminor, sminor, secret = self.logical_id
742
      if target_node not in (pnode, snode):
743
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
744
                                        target_node)
745
      pnode_ip = nodes_ip.get(pnode, None)
746
      snode_ip = nodes_ip.get(snode, None)
747
      if pnode_ip is None or snode_ip is None:
748
        raise errors.ConfigurationError("Can't find primary or secondary node"
749
                                        " for %s" % str(self))
750
      p_data = (pnode_ip, port)
751
      s_data = (snode_ip, port)
752
      if pnode == target_node:
753
        self.physical_id = p_data + s_data + (pminor, secret)
754
      else: # it must be secondary, we tested above
755
        self.physical_id = s_data + p_data + (sminor, secret)
756
    else:
757
      self.physical_id = self.logical_id
758
    return
759

    
760
  def ToDict(self):
761
    """Disk-specific conversion to standard python types.
762

763
    This replaces the children lists of objects with lists of
764
    standard python types.
765

766
    """
767
    bo = super(Disk, self).ToDict()
768

    
769
    for attr in ("children",):
770
      alist = bo.get(attr, None)
771
      if alist:
772
        bo[attr] = self._ContainerToDicts(alist)
773
    return bo
774

    
775
  @classmethod
776
  def FromDict(cls, val):
777
    """Custom function for Disks
778

779
    """
780
    obj = super(Disk, cls).FromDict(val)
781
    if obj.children:
782
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
783
    if obj.logical_id and isinstance(obj.logical_id, list):
784
      obj.logical_id = tuple(obj.logical_id)
785
    if obj.physical_id and isinstance(obj.physical_id, list):
786
      obj.physical_id = tuple(obj.physical_id)
787
    if obj.dev_type in constants.LDS_DRBD:
788
      # we need a tuple of length six here
789
      if len(obj.logical_id) < 6:
790
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
791
    return obj
792

    
793
  def __str__(self):
794
    """Custom str() formatter for disks.
795

796
    """
797
    if self.dev_type == constants.LD_LV:
798
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
799
    elif self.dev_type in constants.LDS_DRBD:
800
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
801
      val = "<DRBD8("
802
      if self.physical_id is None:
803
        phy = "unconfigured"
804
      else:
805
        phy = ("configured as %s:%s %s:%s" %
806
               (self.physical_id[0], self.physical_id[1],
807
                self.physical_id[2], self.physical_id[3]))
808

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

    
828
  def Verify(self):
829
    """Checks that this disk is correctly configured.
830

831
    """
832
    all_errors = []
833
    if self.mode not in constants.DISK_ACCESS_SET:
834
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
835
    return all_errors
836

    
837
  def UpgradeConfig(self):
838
    """Fill defaults for missing configuration values.
839

840
    """
841
    if self.children:
842
      for child in self.children:
843
        child.UpgradeConfig()
844

    
845
    # FIXME: Make this configurable in Ganeti 2.7
846
    self.params = {}
847
    # add here config upgrade for this disk
848

    
849
  @staticmethod
850
  def ComputeLDParams(disk_template, disk_params):
851
    """Computes Logical Disk parameters from Disk Template parameters.
852

853
    @type disk_template: string
854
    @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
855
    @type disk_params: dict
856
    @param disk_params: disk template parameters;
857
                        dict(template_name -> parameters
858
    @rtype: list(dict)
859
    @return: a list of dicts, one for each node of the disk hierarchy. Each dict
860
      contains the LD parameters of the node. The tree is flattened in-order.
861

862
    """
863
    if disk_template not in constants.DISK_TEMPLATES:
864
      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
865

    
866
    assert disk_template in disk_params
867

    
868
    result = list()
869
    dt_params = disk_params[disk_template]
870
    if disk_template == constants.DT_DRBD8:
871
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8], {
872
        constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
873
        constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
874
        constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
875
        constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
876
        constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
877
        constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
878
        constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
879
        constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
880
        constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
881
        constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
882
        constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
883
        constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
884
        }))
885

    
886
      # data LV
887
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
888
        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
889
        }))
890

    
891
      # metadata LV
892
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
893
        constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
894
        }))
895

    
896
    elif disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
897
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
898

    
899
    elif disk_template == constants.DT_PLAIN:
900
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
901
        constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
902
        }))
903

    
904
    elif disk_template == constants.DT_BLOCK:
905
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
906

    
907
    elif disk_template == constants.DT_RBD:
908
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], {
909
        constants.LDP_POOL: dt_params[constants.RBD_POOL],
910
        }))
911

    
912
    elif disk_template == constants.DT_EXT:
913
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_EXT])
914

    
915
    return result
916

    
917

    
918
class InstancePolicy(ConfigObject):
919
  """Config object representing instance policy limits dictionary.
920

921

922
  Note that this object is not actually used in the config, it's just
923
  used as a placeholder for a few functions.
924

925
  """
926
  @classmethod
927
  def CheckParameterSyntax(cls, ipolicy, check_std):
928
    """ Check the instance policy for validity.
929

930
    """
931
    for param in constants.ISPECS_PARAMETERS:
932
      InstancePolicy.CheckISpecSyntax(ipolicy, param, check_std)
933
    if constants.IPOLICY_DTS in ipolicy:
934
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
935
    for key in constants.IPOLICY_PARAMETERS:
936
      if key in ipolicy:
937
        InstancePolicy.CheckParameter(key, ipolicy[key])
938
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
939
    if wrong_keys:
940
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
941
                                      utils.CommaJoin(wrong_keys))
942

    
943
  @classmethod
944
  def CheckISpecSyntax(cls, ipolicy, name, check_std):
945
    """Check the instance policy for validity on a given key.
946

947
    We check if the instance policy makes sense for a given key, that is
948
    if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
949

950
    @type ipolicy: dict
951
    @param ipolicy: dictionary with min, max, std specs
952
    @type name: string
953
    @param name: what are the limits for
954
    @type check_std: bool
955
    @param check_std: Whether to check std value or just assume compliance
956
    @raise errors.ConfigureError: when specs for given name are not valid
957

958
    """
959
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
960

    
961
    if check_std:
962
      std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
963
      std_msg = std_v
964
    else:
965
      std_v = min_v
966
      std_msg = "-"
967

    
968
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
969
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
970
           (name,
971
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
972
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
973
            std_msg))
974
    if min_v > std_v or std_v > max_v:
975
      raise errors.ConfigurationError(err)
976

    
977
  @classmethod
978
  def CheckDiskTemplates(cls, disk_templates):
979
    """Checks the disk templates for validity.
980

981
    """
982
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
983
    if wrong:
984
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
985
                                      utils.CommaJoin(wrong))
986

    
987
  @classmethod
988
  def CheckParameter(cls, key, value):
989
    """Checks a parameter.
990

991
    Currently we expect all parameters to be float values.
992

993
    """
994
    try:
995
      float(value)
996
    except (TypeError, ValueError), err:
997
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
998
                                      " '%s', error: %s" % (key, value, err))
999

    
1000

    
1001
class Instance(TaggableObject):
1002
  """Config object representing an instance."""
1003
  __slots__ = [
1004
    "name",
1005
    "primary_node",
1006
    "os",
1007
    "hypervisor",
1008
    "hvparams",
1009
    "beparams",
1010
    "osparams",
1011
    "admin_state",
1012
    "nics",
1013
    "disks",
1014
    "disk_template",
1015
    "network_port",
1016
    "serial_no",
1017
    ] + _TIMESTAMPS + _UUID
1018

    
1019
  def _ComputeSecondaryNodes(self):
1020
    """Compute the list of secondary nodes.
1021

1022
    This is a simple wrapper over _ComputeAllNodes.
1023

1024
    """
1025
    all_nodes = set(self._ComputeAllNodes())
1026
    all_nodes.discard(self.primary_node)
1027
    return tuple(all_nodes)
1028

    
1029
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1030
                             "List of names of secondary nodes")
1031

    
1032
  def _ComputeAllNodes(self):
1033
    """Compute the list of all nodes.
1034

1035
    Since the data is already there (in the drbd disks), keeping it as
1036
    a separate normal attribute is redundant and if not properly
1037
    synchronised can cause problems. Thus it's better to compute it
1038
    dynamically.
1039

1040
    """
1041
    def _Helper(nodes, device):
1042
      """Recursively computes nodes given a top device."""
1043
      if device.dev_type in constants.LDS_DRBD:
1044
        nodea, nodeb = device.logical_id[:2]
1045
        nodes.add(nodea)
1046
        nodes.add(nodeb)
1047
      if device.children:
1048
        for child in device.children:
1049
          _Helper(nodes, child)
1050

    
1051
    all_nodes = set()
1052
    all_nodes.add(self.primary_node)
1053
    for device in self.disks:
1054
      _Helper(all_nodes, device)
1055
    return tuple(all_nodes)
1056

    
1057
  all_nodes = property(_ComputeAllNodes, None, None,
1058
                       "List of names of all the nodes of the instance")
1059

    
1060
  def MapLVsByNode(self, lvmap=None, devs=None, node=None):
1061
    """Provide a mapping of nodes to LVs this instance owns.
1062

1063
    This function figures out what logical volumes should belong on
1064
    which nodes, recursing through a device tree.
1065

1066
    @param lvmap: optional dictionary to receive the
1067
        'node' : ['lv', ...] data.
1068

1069
    @return: None if lvmap arg is given, otherwise, a dictionary of
1070
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1071
        volumeN is of the form "vg_name/lv_name", compatible with
1072
        GetVolumeList()
1073

1074
    """
1075
    if node is None:
1076
      node = self.primary_node
1077

    
1078
    if lvmap is None:
1079
      lvmap = {
1080
        node: [],
1081
        }
1082
      ret = lvmap
1083
    else:
1084
      if not node in lvmap:
1085
        lvmap[node] = []
1086
      ret = None
1087

    
1088
    if not devs:
1089
      devs = self.disks
1090

    
1091
    for dev in devs:
1092
      if dev.dev_type == constants.LD_LV:
1093
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1094

    
1095
      elif dev.dev_type in constants.LDS_DRBD:
1096
        if dev.children:
1097
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1098
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1099

    
1100
      elif dev.children:
1101
        self.MapLVsByNode(lvmap, dev.children, node)
1102

    
1103
    return ret
1104

    
1105
  def FindDisk(self, idx):
1106
    """Find a disk given having a specified index.
1107

1108
    This is just a wrapper that does validation of the index.
1109

1110
    @type idx: int
1111
    @param idx: the disk index
1112
    @rtype: L{Disk}
1113
    @return: the corresponding disk
1114
    @raise errors.OpPrereqError: when the given index is not valid
1115

1116
    """
1117
    try:
1118
      idx = int(idx)
1119
      return self.disks[idx]
1120
    except (TypeError, ValueError), err:
1121
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1122
                                 errors.ECODE_INVAL)
1123
    except IndexError:
1124
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1125
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1126
                                 errors.ECODE_INVAL)
1127

    
1128
  def ToDict(self):
1129
    """Instance-specific conversion to standard python types.
1130

1131
    This replaces the children lists of objects with lists of standard
1132
    python types.
1133

1134
    """
1135
    bo = super(Instance, self).ToDict()
1136

    
1137
    for attr in "nics", "disks":
1138
      alist = bo.get(attr, None)
1139
      if alist:
1140
        nlist = self._ContainerToDicts(alist)
1141
      else:
1142
        nlist = []
1143
      bo[attr] = nlist
1144
    return bo
1145

    
1146
  @classmethod
1147
  def FromDict(cls, val):
1148
    """Custom function for instances.
1149

1150
    """
1151
    if "admin_state" not in val:
1152
      if val.get("admin_up", False):
1153
        val["admin_state"] = constants.ADMINST_UP
1154
      else:
1155
        val["admin_state"] = constants.ADMINST_DOWN
1156
    if "admin_up" in val:
1157
      del val["admin_up"]
1158
    obj = super(Instance, cls).FromDict(val)
1159
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1160
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1161
    return obj
1162

    
1163
  def UpgradeConfig(self):
1164
    """Fill defaults for missing configuration values.
1165

1166
    """
1167
    for nic in self.nics:
1168
      nic.UpgradeConfig()
1169
    for disk in self.disks:
1170
      disk.UpgradeConfig()
1171
    if self.hvparams:
1172
      for key in constants.HVC_GLOBALS:
1173
        try:
1174
          del self.hvparams[key]
1175
        except KeyError:
1176
          pass
1177
    if self.osparams is None:
1178
      self.osparams = {}
1179
    UpgradeBeParams(self.beparams)
1180

    
1181

    
1182
class OS(ConfigObject):
1183
  """Config object representing an operating system.
1184

1185
  @type supported_parameters: list
1186
  @ivar supported_parameters: a list of tuples, name and description,
1187
      containing the supported parameters by this OS
1188

1189
  @type VARIANT_DELIM: string
1190
  @cvar VARIANT_DELIM: the variant delimiter
1191

1192
  """
1193
  __slots__ = [
1194
    "name",
1195
    "path",
1196
    "api_versions",
1197
    "create_script",
1198
    "export_script",
1199
    "import_script",
1200
    "rename_script",
1201
    "verify_script",
1202
    "supported_variants",
1203
    "supported_parameters",
1204
    ]
1205

    
1206
  VARIANT_DELIM = "+"
1207

    
1208
  @classmethod
1209
  def SplitNameVariant(cls, name):
1210
    """Splits the name into the proper name and variant.
1211

1212
    @param name: the OS (unprocessed) name
1213
    @rtype: list
1214
    @return: a list of two elements; if the original name didn't
1215
        contain a variant, it's returned as an empty string
1216

1217
    """
1218
    nv = name.split(cls.VARIANT_DELIM, 1)
1219
    if len(nv) == 1:
1220
      nv.append("")
1221
    return nv
1222

    
1223
  @classmethod
1224
  def GetName(cls, name):
1225
    """Returns the proper name of the os (without the variant).
1226

1227
    @param name: the OS (unprocessed) name
1228

1229
    """
1230
    return cls.SplitNameVariant(name)[0]
1231

    
1232
  @classmethod
1233
  def GetVariant(cls, name):
1234
    """Returns the variant the os (without the base name).
1235

1236
    @param name: the OS (unprocessed) name
1237

1238
    """
1239
    return cls.SplitNameVariant(name)[1]
1240

    
1241

    
1242
class ExtStorage(ConfigObject):
1243
  """Config object representing an External Storage Provider.
1244

1245
  """
1246
  __slots__ = [
1247
    "name",
1248
    "path",
1249
    "create_script",
1250
    "remove_script",
1251
    "grow_script",
1252
    "attach_script",
1253
    "detach_script",
1254
    "setinfo_script",
1255
    "verify_script",
1256
    "supported_parameters",
1257
    ]
1258

    
1259

    
1260
class NodeHvState(ConfigObject):
1261
  """Hypvervisor state on a node.
1262

1263
  @ivar mem_total: Total amount of memory
1264
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1265
    available)
1266
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1267
    rounding
1268
  @ivar mem_inst: Memory used by instances living on node
1269
  @ivar cpu_total: Total node CPU core count
1270
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1271

1272
  """
1273
  __slots__ = [
1274
    "mem_total",
1275
    "mem_node",
1276
    "mem_hv",
1277
    "mem_inst",
1278
    "cpu_total",
1279
    "cpu_node",
1280
    ] + _TIMESTAMPS
1281

    
1282

    
1283
class NodeDiskState(ConfigObject):
1284
  """Disk state on a node.
1285

1286
  """
1287
  __slots__ = [
1288
    "total",
1289
    "reserved",
1290
    "overhead",
1291
    ] + _TIMESTAMPS
1292

    
1293

    
1294
class Node(TaggableObject):
1295
  """Config object representing a node.
1296

1297
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1298
  @ivar hv_state_static: Hypervisor state overriden by user
1299
  @ivar disk_state: Disk state (e.g. free space)
1300
  @ivar disk_state_static: Disk state overriden by user
1301

1302
  """
1303
  __slots__ = [
1304
    "name",
1305
    "primary_ip",
1306
    "secondary_ip",
1307
    "serial_no",
1308
    "master_candidate",
1309
    "offline",
1310
    "drained",
1311
    "group",
1312
    "master_capable",
1313
    "vm_capable",
1314
    "ndparams",
1315
    "powered",
1316
    "hv_state",
1317
    "hv_state_static",
1318
    "disk_state",
1319
    "disk_state_static",
1320
    ] + _TIMESTAMPS + _UUID
1321

    
1322
  def UpgradeConfig(self):
1323
    """Fill defaults for missing configuration values.
1324

1325
    """
1326
    # pylint: disable=E0203
1327
    # because these are "defined" via slots, not manually
1328
    if self.master_capable is None:
1329
      self.master_capable = True
1330

    
1331
    if self.vm_capable is None:
1332
      self.vm_capable = True
1333

    
1334
    if self.ndparams is None:
1335
      self.ndparams = {}
1336

    
1337
    if self.powered is None:
1338
      self.powered = True
1339

    
1340
  def ToDict(self):
1341
    """Custom function for serializing.
1342

1343
    """
1344
    data = super(Node, self).ToDict()
1345

    
1346
    hv_state = data.get("hv_state", None)
1347
    if hv_state is not None:
1348
      data["hv_state"] = self._ContainerToDicts(hv_state)
1349

    
1350
    disk_state = data.get("disk_state", None)
1351
    if disk_state is not None:
1352
      data["disk_state"] = \
1353
        dict((key, self._ContainerToDicts(value))
1354
             for (key, value) in disk_state.items())
1355

    
1356
    return data
1357

    
1358
  @classmethod
1359
  def FromDict(cls, val):
1360
    """Custom function for deserializing.
1361

1362
    """
1363
    obj = super(Node, cls).FromDict(val)
1364

    
1365
    if obj.hv_state is not None:
1366
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1367

    
1368
    if obj.disk_state is not None:
1369
      obj.disk_state = \
1370
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1371
             for (key, value) in obj.disk_state.items())
1372

    
1373
    return obj
1374

    
1375

    
1376
class NodeGroup(TaggableObject):
1377
  """Config object representing a node group."""
1378
  __slots__ = [
1379
    "name",
1380
    "members",
1381
    "ndparams",
1382
    "diskparams",
1383
    "ipolicy",
1384
    "serial_no",
1385
    "hv_state_static",
1386
    "disk_state_static",
1387
    "alloc_policy",
1388
    "networks",
1389
    ] + _TIMESTAMPS + _UUID
1390

    
1391
  def ToDict(self):
1392
    """Custom function for nodegroup.
1393

1394
    This discards the members object, which gets recalculated and is only kept
1395
    in memory.
1396

1397
    """
1398
    mydict = super(NodeGroup, self).ToDict()
1399
    del mydict["members"]
1400
    return mydict
1401

    
1402
  @classmethod
1403
  def FromDict(cls, val):
1404
    """Custom function for nodegroup.
1405

1406
    The members slot is initialized to an empty list, upon deserialization.
1407

1408
    """
1409
    obj = super(NodeGroup, cls).FromDict(val)
1410
    obj.members = []
1411
    return obj
1412

    
1413
  def UpgradeConfig(self):
1414
    """Fill defaults for missing configuration values.
1415

1416
    """
1417
    if self.ndparams is None:
1418
      self.ndparams = {}
1419

    
1420
    if self.serial_no is None:
1421
      self.serial_no = 1
1422

    
1423
    if self.alloc_policy is None:
1424
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1425

    
1426
    # We only update mtime, and not ctime, since we would not be able
1427
    # to provide a correct value for creation time.
1428
    if self.mtime is None:
1429
      self.mtime = time.time()
1430

    
1431
    if self.diskparams is None:
1432
      self.diskparams = {}
1433
    if self.ipolicy is None:
1434
      self.ipolicy = MakeEmptyIPolicy()
1435

    
1436
    if self.networks is None:
1437
      self.networks = {}
1438

    
1439
  def FillND(self, node):
1440
    """Return filled out ndparams for L{objects.Node}
1441

1442
    @type node: L{objects.Node}
1443
    @param node: A Node object to fill
1444
    @return a copy of the node's ndparams with defaults filled
1445

1446
    """
1447
    return self.SimpleFillND(node.ndparams)
1448

    
1449
  def SimpleFillND(self, ndparams):
1450
    """Fill a given ndparams dict with defaults.
1451

1452
    @type ndparams: dict
1453
    @param ndparams: the dict to fill
1454
    @rtype: dict
1455
    @return: a copy of the passed in ndparams with missing keys filled
1456
        from the node group defaults
1457

1458
    """
1459
    return FillDict(self.ndparams, ndparams)
1460

    
1461

    
1462
class Cluster(TaggableObject):
1463
  """Config object representing the cluster."""
1464
  __slots__ = [
1465
    "serial_no",
1466
    "rsahostkeypub",
1467
    "highest_used_port",
1468
    "tcpudp_port_pool",
1469
    "mac_prefix",
1470
    "volume_group_name",
1471
    "reserved_lvs",
1472
    "drbd_usermode_helper",
1473
    "default_bridge",
1474
    "default_hypervisor",
1475
    "master_node",
1476
    "master_ip",
1477
    "master_netdev",
1478
    "master_netmask",
1479
    "use_external_mip_script",
1480
    "cluster_name",
1481
    "file_storage_dir",
1482
    "shared_file_storage_dir",
1483
    "enabled_hypervisors",
1484
    "hvparams",
1485
    "ipolicy",
1486
    "os_hvp",
1487
    "beparams",
1488
    "osparams",
1489
    "nicparams",
1490
    "ndparams",
1491
    "diskparams",
1492
    "candidate_pool_size",
1493
    "modify_etc_hosts",
1494
    "modify_ssh_setup",
1495
    "maintain_node_health",
1496
    "uid_pool",
1497
    "default_iallocator",
1498
    "hidden_os",
1499
    "blacklisted_os",
1500
    "primary_ip_family",
1501
    "prealloc_wipe_disks",
1502
    "hv_state_static",
1503
    "disk_state_static",
1504
    ] + _TIMESTAMPS + _UUID
1505

    
1506
  def UpgradeConfig(self):
1507
    """Fill defaults for missing configuration values.
1508

1509
    """
1510
    # pylint: disable=E0203
1511
    # because these are "defined" via slots, not manually
1512
    if self.hvparams is None:
1513
      self.hvparams = constants.HVC_DEFAULTS
1514
    else:
1515
      for hypervisor in self.hvparams:
1516
        self.hvparams[hypervisor] = FillDict(
1517
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1518

    
1519
    if self.os_hvp is None:
1520
      self.os_hvp = {}
1521

    
1522
    # osparams added before 2.2
1523
    if self.osparams is None:
1524
      self.osparams = {}
1525

    
1526
    self.ndparams = UpgradeNDParams(self.ndparams)
1527

    
1528
    self.beparams = UpgradeGroupedParams(self.beparams,
1529
                                         constants.BEC_DEFAULTS)
1530
    for beparams_group in self.beparams:
1531
      UpgradeBeParams(self.beparams[beparams_group])
1532

    
1533
    migrate_default_bridge = not self.nicparams
1534
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1535
                                          constants.NICC_DEFAULTS)
1536
    if migrate_default_bridge:
1537
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1538
        self.default_bridge
1539

    
1540
    if self.modify_etc_hosts is None:
1541
      self.modify_etc_hosts = True
1542

    
1543
    if self.modify_ssh_setup is None:
1544
      self.modify_ssh_setup = True
1545

    
1546
    # default_bridge is no longer used in 2.1. The slot is left there to
1547
    # support auto-upgrading. It can be removed once we decide to deprecate
1548
    # upgrading straight from 2.0.
1549
    if self.default_bridge is not None:
1550
      self.default_bridge = None
1551

    
1552
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1553
    # code can be removed once upgrading straight from 2.0 is deprecated.
1554
    if self.default_hypervisor is not None:
1555
      self.enabled_hypervisors = ([self.default_hypervisor] +
1556
                                  [hvname for hvname in self.enabled_hypervisors
1557
                                   if hvname != self.default_hypervisor])
1558
      self.default_hypervisor = None
1559

    
1560
    # maintain_node_health added after 2.1.1
1561
    if self.maintain_node_health is None:
1562
      self.maintain_node_health = False
1563

    
1564
    if self.uid_pool is None:
1565
      self.uid_pool = []
1566

    
1567
    if self.default_iallocator is None:
1568
      self.default_iallocator = ""
1569

    
1570
    # reserved_lvs added before 2.2
1571
    if self.reserved_lvs is None:
1572
      self.reserved_lvs = []
1573

    
1574
    # hidden and blacklisted operating systems added before 2.2.1
1575
    if self.hidden_os is None:
1576
      self.hidden_os = []
1577

    
1578
    if self.blacklisted_os is None:
1579
      self.blacklisted_os = []
1580

    
1581
    # primary_ip_family added before 2.3
1582
    if self.primary_ip_family is None:
1583
      self.primary_ip_family = AF_INET
1584

    
1585
    if self.master_netmask is None:
1586
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1587
      self.master_netmask = ipcls.iplen
1588

    
1589
    if self.prealloc_wipe_disks is None:
1590
      self.prealloc_wipe_disks = False
1591

    
1592
    # shared_file_storage_dir added before 2.5
1593
    if self.shared_file_storage_dir is None:
1594
      self.shared_file_storage_dir = ""
1595

    
1596
    if self.use_external_mip_script is None:
1597
      self.use_external_mip_script = False
1598

    
1599
    if self.diskparams:
1600
      self.diskparams = UpgradeDiskParams(self.diskparams)
1601
    else:
1602
      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1603

    
1604
    # instance policy added before 2.6
1605
    if self.ipolicy is None:
1606
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1607
    else:
1608
      # we can either make sure to upgrade the ipolicy always, or only
1609
      # do it in some corner cases (e.g. missing keys); note that this
1610
      # will break any removal of keys from the ipolicy dict
1611
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1612

    
1613
  @property
1614
  def primary_hypervisor(self):
1615
    """The first hypervisor is the primary.
1616

1617
    Useful, for example, for L{Node}'s hv/disk state.
1618

1619
    """
1620
    return self.enabled_hypervisors[0]
1621

    
1622
  def ToDict(self):
1623
    """Custom function for cluster.
1624

1625
    """
1626
    mydict = super(Cluster, self).ToDict()
1627
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1628
    return mydict
1629

    
1630
  @classmethod
1631
  def FromDict(cls, val):
1632
    """Custom function for cluster.
1633

1634
    """
1635
    obj = super(Cluster, cls).FromDict(val)
1636
    if not isinstance(obj.tcpudp_port_pool, set):
1637
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1638
    return obj
1639

    
1640
  def SimpleFillDP(self, diskparams):
1641
    """Fill a given diskparams dict with cluster defaults.
1642

1643
    @param diskparams: The diskparams
1644
    @return: The defaults dict
1645

1646
    """
1647
    return FillDiskParams(self.diskparams, diskparams)
1648

    
1649
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1650
    """Get the default hypervisor parameters for the cluster.
1651

1652
    @param hypervisor: the hypervisor name
1653
    @param os_name: if specified, we'll also update the defaults for this OS
1654
    @param skip_keys: if passed, list of keys not to use
1655
    @return: the defaults dict
1656

1657
    """
1658
    if skip_keys is None:
1659
      skip_keys = []
1660

    
1661
    fill_stack = [self.hvparams.get(hypervisor, {})]
1662
    if os_name is not None:
1663
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1664
      fill_stack.append(os_hvp)
1665

    
1666
    ret_dict = {}
1667
    for o_dict in fill_stack:
1668
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1669

    
1670
    return ret_dict
1671

    
1672
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1673
    """Fill a given hvparams dict with cluster defaults.
1674

1675
    @type hv_name: string
1676
    @param hv_name: the hypervisor to use
1677
    @type os_name: string
1678
    @param os_name: the OS to use for overriding the hypervisor defaults
1679
    @type skip_globals: boolean
1680
    @param skip_globals: if True, the global hypervisor parameters will
1681
        not be filled
1682
    @rtype: dict
1683
    @return: a copy of the given hvparams with missing keys filled from
1684
        the cluster defaults
1685

1686
    """
1687
    if skip_globals:
1688
      skip_keys = constants.HVC_GLOBALS
1689
    else:
1690
      skip_keys = []
1691

    
1692
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1693
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1694

    
1695
  def FillHV(self, instance, skip_globals=False):
1696
    """Fill an instance's hvparams dict with cluster defaults.
1697

1698
    @type instance: L{objects.Instance}
1699
    @param instance: the instance parameter to fill
1700
    @type skip_globals: boolean
1701
    @param skip_globals: if True, the global hypervisor parameters will
1702
        not be filled
1703
    @rtype: dict
1704
    @return: a copy of the instance's hvparams with missing keys filled from
1705
        the cluster defaults
1706

1707
    """
1708
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1709
                             instance.hvparams, skip_globals)
1710

    
1711
  def SimpleFillBE(self, beparams):
1712
    """Fill a given beparams dict with cluster defaults.
1713

1714
    @type beparams: dict
1715
    @param beparams: the dict to fill
1716
    @rtype: dict
1717
    @return: a copy of the passed in beparams with missing keys filled
1718
        from the cluster defaults
1719

1720
    """
1721
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1722

    
1723
  def FillBE(self, instance):
1724
    """Fill an instance's beparams dict with cluster defaults.
1725

1726
    @type instance: L{objects.Instance}
1727
    @param instance: the instance parameter to fill
1728
    @rtype: dict
1729
    @return: a copy of the instance's beparams with missing keys filled from
1730
        the cluster defaults
1731

1732
    """
1733
    return self.SimpleFillBE(instance.beparams)
1734

    
1735
  def SimpleFillNIC(self, nicparams):
1736
    """Fill a given nicparams dict with cluster defaults.
1737

1738
    @type nicparams: dict
1739
    @param nicparams: the dict to fill
1740
    @rtype: dict
1741
    @return: a copy of the passed in nicparams with missing keys filled
1742
        from the cluster defaults
1743

1744
    """
1745
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1746

    
1747
  def SimpleFillOS(self, os_name, os_params):
1748
    """Fill an instance's osparams dict with cluster defaults.
1749

1750
    @type os_name: string
1751
    @param os_name: the OS name to use
1752
    @type os_params: dict
1753
    @param os_params: the dict to fill with default values
1754
    @rtype: dict
1755
    @return: a copy of the instance's osparams with missing keys filled from
1756
        the cluster defaults
1757

1758
    """
1759
    name_only = os_name.split("+", 1)[0]
1760
    # base OS
1761
    result = self.osparams.get(name_only, {})
1762
    # OS with variant
1763
    result = FillDict(result, self.osparams.get(os_name, {}))
1764
    # specified params
1765
    return FillDict(result, os_params)
1766

    
1767
  @staticmethod
1768
  def SimpleFillHvState(hv_state):
1769
    """Fill an hv_state sub dict with cluster defaults.
1770

1771
    """
1772
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1773

    
1774
  @staticmethod
1775
  def SimpleFillDiskState(disk_state):
1776
    """Fill an disk_state sub dict with cluster defaults.
1777

1778
    """
1779
    return FillDict(constants.DS_DEFAULTS, disk_state)
1780

    
1781
  def FillND(self, node, nodegroup):
1782
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1783

1784
    @type node: L{objects.Node}
1785
    @param node: A Node object to fill
1786
    @type nodegroup: L{objects.NodeGroup}
1787
    @param nodegroup: A Node object to fill
1788
    @return a copy of the node's ndparams with defaults filled
1789

1790
    """
1791
    return self.SimpleFillND(nodegroup.FillND(node))
1792

    
1793
  def SimpleFillND(self, ndparams):
1794
    """Fill a given ndparams dict with defaults.
1795

1796
    @type ndparams: dict
1797
    @param ndparams: the dict to fill
1798
    @rtype: dict
1799
    @return: a copy of the passed in ndparams with missing keys filled
1800
        from the cluster defaults
1801

1802
    """
1803
    return FillDict(self.ndparams, ndparams)
1804

    
1805
  def SimpleFillIPolicy(self, ipolicy):
1806
    """ Fill instance policy dict with defaults.
1807

1808
    @type ipolicy: dict
1809
    @param ipolicy: the dict to fill
1810
    @rtype: dict
1811
    @return: a copy of passed ipolicy with missing keys filled from
1812
      the cluster defaults
1813

1814
    """
1815
    return FillIPolicy(self.ipolicy, ipolicy)
1816

    
1817

    
1818
class BlockDevStatus(ConfigObject):
1819
  """Config object representing the status of a block device."""
1820
  __slots__ = [
1821
    "dev_path",
1822
    "major",
1823
    "minor",
1824
    "sync_percent",
1825
    "estimated_time",
1826
    "is_degraded",
1827
    "ldisk_status",
1828
    ]
1829

    
1830

    
1831
class ImportExportStatus(ConfigObject):
1832
  """Config object representing the status of an import or export."""
1833
  __slots__ = [
1834
    "recent_output",
1835
    "listen_port",
1836
    "connected",
1837
    "progress_mbytes",
1838
    "progress_throughput",
1839
    "progress_eta",
1840
    "progress_percent",
1841
    "exit_status",
1842
    "error_message",
1843
    ] + _TIMESTAMPS
1844

    
1845

    
1846
class ImportExportOptions(ConfigObject):
1847
  """Options for import/export daemon
1848

1849
  @ivar key_name: X509 key name (None for cluster certificate)
1850
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1851
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1852
  @ivar magic: Used to ensure the connection goes to the right disk
1853
  @ivar ipv6: Whether to use IPv6
1854
  @ivar connect_timeout: Number of seconds for establishing connection
1855

1856
  """
1857
  __slots__ = [
1858
    "key_name",
1859
    "ca_pem",
1860
    "compress",
1861
    "magic",
1862
    "ipv6",
1863
    "connect_timeout",
1864
    ]
1865

    
1866

    
1867
class ConfdRequest(ConfigObject):
1868
  """Object holding a confd request.
1869

1870
  @ivar protocol: confd protocol version
1871
  @ivar type: confd query type
1872
  @ivar query: query request
1873
  @ivar rsalt: requested reply salt
1874

1875
  """
1876
  __slots__ = [
1877
    "protocol",
1878
    "type",
1879
    "query",
1880
    "rsalt",
1881
    ]
1882

    
1883

    
1884
class ConfdReply(ConfigObject):
1885
  """Object holding a confd reply.
1886

1887
  @ivar protocol: confd protocol version
1888
  @ivar status: reply status code (ok, error)
1889
  @ivar answer: confd query reply
1890
  @ivar serial: configuration serial number
1891

1892
  """
1893
  __slots__ = [
1894
    "protocol",
1895
    "status",
1896
    "answer",
1897
    "serial",
1898
    ]
1899

    
1900

    
1901
class QueryFieldDefinition(ConfigObject):
1902
  """Object holding a query field definition.
1903

1904
  @ivar name: Field name
1905
  @ivar title: Human-readable title
1906
  @ivar kind: Field type
1907
  @ivar doc: Human-readable description
1908

1909
  """
1910
  __slots__ = [
1911
    "name",
1912
    "title",
1913
    "kind",
1914
    "doc",
1915
    ]
1916

    
1917

    
1918
class _QueryResponseBase(ConfigObject):
1919
  __slots__ = [
1920
    "fields",
1921
    ]
1922

    
1923
  def ToDict(self):
1924
    """Custom function for serializing.
1925

1926
    """
1927
    mydict = super(_QueryResponseBase, self).ToDict()
1928
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1929
    return mydict
1930

    
1931
  @classmethod
1932
  def FromDict(cls, val):
1933
    """Custom function for de-serializing.
1934

1935
    """
1936
    obj = super(_QueryResponseBase, cls).FromDict(val)
1937
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1938
    return obj
1939

    
1940

    
1941
class QueryResponse(_QueryResponseBase):
1942
  """Object holding the response to a query.
1943

1944
  @ivar fields: List of L{QueryFieldDefinition} objects
1945
  @ivar data: Requested data
1946

1947
  """
1948
  __slots__ = [
1949
    "data",
1950
    ]
1951

    
1952

    
1953
class QueryFieldsRequest(ConfigObject):
1954
  """Object holding a request for querying available fields.
1955

1956
  """
1957
  __slots__ = [
1958
    "what",
1959
    "fields",
1960
    ]
1961

    
1962

    
1963
class QueryFieldsResponse(_QueryResponseBase):
1964
  """Object holding the response to a query for fields.
1965

1966
  @ivar fields: List of L{QueryFieldDefinition} objects
1967

1968
  """
1969
  __slots__ = []
1970

    
1971

    
1972
class MigrationStatus(ConfigObject):
1973
  """Object holding the status of a migration.
1974

1975
  """
1976
  __slots__ = [
1977
    "status",
1978
    "transferred_ram",
1979
    "total_ram",
1980
    ]
1981

    
1982

    
1983
class InstanceConsole(ConfigObject):
1984
  """Object describing how to access the console of an instance.
1985

1986
  """
1987
  __slots__ = [
1988
    "instance",
1989
    "kind",
1990
    "message",
1991
    "host",
1992
    "port",
1993
    "user",
1994
    "command",
1995
    "display",
1996
    ]
1997

    
1998
  def Validate(self):
1999
    """Validates contents of this object.
2000

2001
    """
2002
    assert self.kind in constants.CONS_ALL, "Unknown console type"
2003
    assert self.instance, "Missing instance name"
2004
    assert self.message or self.kind in [constants.CONS_SSH,
2005
                                         constants.CONS_SPICE,
2006
                                         constants.CONS_VNC]
2007
    assert self.host or self.kind == constants.CONS_MESSAGE
2008
    assert self.port or self.kind in [constants.CONS_MESSAGE,
2009
                                      constants.CONS_SSH]
2010
    assert self.user or self.kind in [constants.CONS_MESSAGE,
2011
                                      constants.CONS_SPICE,
2012
                                      constants.CONS_VNC]
2013
    assert self.command or self.kind in [constants.CONS_MESSAGE,
2014
                                         constants.CONS_SPICE,
2015
                                         constants.CONS_VNC]
2016
    assert self.display or self.kind in [constants.CONS_MESSAGE,
2017
                                         constants.CONS_SPICE,
2018
                                         constants.CONS_SSH]
2019
    return True
2020

    
2021

    
2022
class Network(TaggableObject):
2023
  """Object representing a network definition for ganeti.
2024

2025
  """
2026
  __slots__ = [
2027
    "name",
2028
    "serial_no",
2029
    "network_type",
2030
    "mac_prefix",
2031
    "family",
2032
    "network",
2033
    "network6",
2034
    "gateway",
2035
    "gateway6",
2036
    "size",
2037
    "reservations",
2038
    "ext_reservations",
2039
    ] + _TIMESTAMPS + _UUID
2040

    
2041

    
2042
class SerializableConfigParser(ConfigParser.SafeConfigParser):
2043
  """Simple wrapper over ConfigParse that allows serialization.
2044

2045
  This class is basically ConfigParser.SafeConfigParser with two
2046
  additional methods that allow it to serialize/unserialize to/from a
2047
  buffer.
2048

2049
  """
2050
  def Dumps(self):
2051
    """Dump this instance and return the string representation."""
2052
    buf = StringIO()
2053
    self.write(buf)
2054
    return buf.getvalue()
2055

    
2056
  @classmethod
2057
  def Loads(cls, data):
2058
    """Load data from a string."""
2059
    buf = StringIO(data)
2060
    cfp = cls()
2061
    cfp.readfp(buf)
2062
    return cfp