Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 473ab806

History | View | Annotate | Download (58 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
  def Copy(self):
267
    """Makes a deep copy of the current object and its children.
268

269
    """
270
    dict_form = self.ToDict()
271
    clone_obj = self.__class__.FromDict(dict_form)
272
    return clone_obj
273

    
274
  def __repr__(self):
275
    """Implement __repr__ for ConfigObjects."""
276
    return repr(self.ToDict())
277

    
278
  def UpgradeConfig(self):
279
    """Fill defaults for missing configuration values.
280

281
    This method will be called at configuration load time, and its
282
    implementation will be object dependent.
283

284
    """
285
    pass
286

    
287

    
288
class TaggableObject(ConfigObject):
289
  """An generic class supporting tags.
290

291
  """
292
  __slots__ = ["tags"]
293
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
294

    
295
  @classmethod
296
  def ValidateTag(cls, tag):
297
    """Check if a tag is valid.
298

299
    If the tag is invalid, an errors.TagError will be raised. The
300
    function has no return value.
301

302
    """
303
    if not isinstance(tag, basestring):
304
      raise errors.TagError("Invalid tag type (not a string)")
305
    if len(tag) > constants.MAX_TAG_LEN:
306
      raise errors.TagError("Tag too long (>%d characters)" %
307
                            constants.MAX_TAG_LEN)
308
    if not tag:
309
      raise errors.TagError("Tags cannot be empty")
310
    if not cls.VALID_TAG_RE.match(tag):
311
      raise errors.TagError("Tag contains invalid characters")
312

    
313
  def GetTags(self):
314
    """Return the tags list.
315

316
    """
317
    tags = getattr(self, "tags", None)
318
    if tags is None:
319
      tags = self.tags = set()
320
    return tags
321

    
322
  def AddTag(self, tag):
323
    """Add a new tag.
324

325
    """
326
    self.ValidateTag(tag)
327
    tags = self.GetTags()
328
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
329
      raise errors.TagError("Too many tags")
330
    self.GetTags().add(tag)
331

    
332
  def RemoveTag(self, tag):
333
    """Remove a tag.
334

335
    """
336
    self.ValidateTag(tag)
337
    tags = self.GetTags()
338
    try:
339
      tags.remove(tag)
340
    except KeyError:
341
      raise errors.TagError("Tag not found")
342

    
343
  def ToDict(self):
344
    """Taggable-object-specific conversion to standard python types.
345

346
    This replaces the tags set with a list.
347

348
    """
349
    bo = super(TaggableObject, self).ToDict()
350

    
351
    tags = bo.get("tags", None)
352
    if isinstance(tags, set):
353
      bo["tags"] = list(tags)
354
    return bo
355

    
356
  @classmethod
357
  def FromDict(cls, val):
358
    """Custom function for instances.
359

360
    """
361
    obj = super(TaggableObject, cls).FromDict(val)
362
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
363
      obj.tags = set(obj.tags)
364
    return obj
365

    
366

    
367
class MasterNetworkParameters(ConfigObject):
368
  """Network configuration parameters for the master
369

370
  @ivar name: master name
371
  @ivar ip: master IP
372
  @ivar netmask: master netmask
373
  @ivar netdev: master network device
374
  @ivar ip_family: master IP family
375

376
  """
377
  __slots__ = [
378
    "name",
379
    "ip",
380
    "netmask",
381
    "netdev",
382
    "ip_family",
383
    ]
384

    
385

    
386
class ConfigData(ConfigObject):
387
  """Top-level config object."""
388
  __slots__ = [
389
    "version",
390
    "cluster",
391
    "nodes",
392
    "nodegroups",
393
    "instances",
394
    "networks",
395
    "serial_no",
396
    ] + _TIMESTAMPS
397

    
398
  def ToDict(self):
399
    """Custom function for top-level config data.
400

401
    This just replaces the list of instances, nodes and the cluster
402
    with standard python types.
403

404
    """
405
    mydict = super(ConfigData, self).ToDict()
406
    mydict["cluster"] = mydict["cluster"].ToDict()
407
    for key in "nodes", "instances", "nodegroups", "networks":
408
      mydict[key] = objectutils.ContainerToDicts(mydict[key])
409

    
410
    return mydict
411

    
412
  @classmethod
413
  def FromDict(cls, val):
414
    """Custom function for top-level config data
415

416
    """
417
    obj = super(ConfigData, cls).FromDict(val)
418
    obj.cluster = Cluster.FromDict(obj.cluster)
419
    obj.nodes = objectutils.ContainerFromDicts(obj.nodes, dict, Node)
420
    obj.instances = \
421
      objectutils.ContainerFromDicts(obj.instances, dict, Instance)
422
    obj.nodegroups = \
423
      objectutils.ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
424
    obj.networks = objectutils.ContainerFromDicts(obj.networks, dict, Network)
425
    return obj
426

    
427
  def HasAnyDiskOfType(self, dev_type):
428
    """Check if in there is at disk of the given type in the configuration.
429

430
    @type dev_type: L{constants.LDS_BLOCK}
431
    @param dev_type: the type to look for
432
    @rtype: boolean
433
    @return: boolean indicating if a disk of the given type was found or not
434

435
    """
436
    for instance in self.instances.values():
437
      for disk in instance.disks:
438
        if disk.IsBasedOnDiskType(dev_type):
439
          return True
440
    return False
441

    
442
  def UpgradeConfig(self):
443
    """Fill defaults for missing configuration values.
444

445
    """
446
    self.cluster.UpgradeConfig()
447
    for node in self.nodes.values():
448
      node.UpgradeConfig()
449
    for instance in self.instances.values():
450
      instance.UpgradeConfig()
451
    if self.nodegroups is None:
452
      self.nodegroups = {}
453
    for nodegroup in self.nodegroups.values():
454
      nodegroup.UpgradeConfig()
455
    if self.cluster.drbd_usermode_helper is None:
456
      # To decide if we set an helper let's check if at least one instance has
457
      # a DRBD disk. This does not cover all the possible scenarios but it
458
      # gives a good approximation.
459
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
460
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
461
    if self.networks is None:
462
      self.networks = {}
463

    
464

    
465
class NIC(ConfigObject):
466
  """Config object representing a network card."""
467
  __slots__ = ["mac", "ip", "network", "nicparams", "netinfo"]
468

    
469
  @classmethod
470
  def CheckParameterSyntax(cls, nicparams):
471
    """Check the given parameters for validity.
472

473
    @type nicparams:  dict
474
    @param nicparams: dictionary with parameter names/value
475
    @raise errors.ConfigurationError: when a parameter is not valid
476

477
    """
478
    mode = nicparams[constants.NIC_MODE]
479
    if (mode not in constants.NIC_VALID_MODES and
480
        mode != constants.VALUE_AUTO):
481
      raise errors.ConfigurationError("Invalid NIC mode '%s'" % mode)
482

    
483
    if (mode == constants.NIC_MODE_BRIDGED and
484
        not nicparams[constants.NIC_LINK]):
485
      raise errors.ConfigurationError("Missing bridged NIC link")
486

    
487

    
488
class Disk(ConfigObject):
489
  """Config object representing a block device."""
490
  __slots__ = ["dev_type", "logical_id", "physical_id",
491
               "children", "iv_name", "size", "mode", "params"]
492

    
493
  def CreateOnSecondary(self):
494
    """Test if this device needs to be created on a secondary node."""
495
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
496

    
497
  def AssembleOnSecondary(self):
498
    """Test if this device needs to be assembled on a secondary node."""
499
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
500

    
501
  def OpenOnSecondary(self):
502
    """Test if this device needs to be opened on a secondary node."""
503
    return self.dev_type in (constants.LD_LV,)
504

    
505
  def StaticDevPath(self):
506
    """Return the device path if this device type has a static one.
507

508
    Some devices (LVM for example) live always at the same /dev/ path,
509
    irrespective of their status. For such devices, we return this
510
    path, for others we return None.
511

512
    @warning: The path returned is not a normalized pathname; callers
513
        should check that it is a valid path.
514

515
    """
516
    if self.dev_type == constants.LD_LV:
517
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
518
    elif self.dev_type == constants.LD_BLOCKDEV:
519
      return self.logical_id[1]
520
    elif self.dev_type == constants.LD_RBD:
521
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
522
    return None
523

    
524
  def ChildrenNeeded(self):
525
    """Compute the needed number of children for activation.
526

527
    This method will return either -1 (all children) or a positive
528
    number denoting the minimum number of children needed for
529
    activation (only mirrored devices will usually return >=0).
530

531
    Currently, only DRBD8 supports diskless activation (therefore we
532
    return 0), for all other we keep the previous semantics and return
533
    -1.
534

535
    """
536
    if self.dev_type == constants.LD_DRBD8:
537
      return 0
538
    return -1
539

    
540
  def IsBasedOnDiskType(self, dev_type):
541
    """Check if the disk or its children are based on the given type.
542

543
    @type dev_type: L{constants.LDS_BLOCK}
544
    @param dev_type: the type to look for
545
    @rtype: boolean
546
    @return: boolean indicating if a device of the given type was found or not
547

548
    """
549
    if self.children:
550
      for child in self.children:
551
        if child.IsBasedOnDiskType(dev_type):
552
          return True
553
    return self.dev_type == dev_type
554

    
555
  def GetNodes(self, node):
556
    """This function returns the nodes this device lives on.
557

558
    Given the node on which the parent of the device lives on (or, in
559
    case of a top-level device, the primary node of the devices'
560
    instance), this function will return a list of nodes on which this
561
    devices needs to (or can) be assembled.
562

563
    """
564
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
565
                         constants.LD_BLOCKDEV, constants.LD_RBD,
566
                         constants.LD_EXT]:
567
      result = [node]
568
    elif self.dev_type in constants.LDS_DRBD:
569
      result = [self.logical_id[0], self.logical_id[1]]
570
      if node not in result:
571
        raise errors.ConfigurationError("DRBD device passed unknown node")
572
    else:
573
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
574
    return result
575

    
576
  def ComputeNodeTree(self, parent_node):
577
    """Compute the node/disk tree for this disk and its children.
578

579
    This method, given the node on which the parent disk lives, will
580
    return the list of all (node, disk) pairs which describe the disk
581
    tree in the most compact way. For example, a drbd/lvm stack
582
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
583
    which represents all the top-level devices on the nodes.
584

585
    """
586
    my_nodes = self.GetNodes(parent_node)
587
    result = [(node, self) for node in my_nodes]
588
    if not self.children:
589
      # leaf device
590
      return result
591
    for node in my_nodes:
592
      for child in self.children:
593
        child_result = child.ComputeNodeTree(node)
594
        if len(child_result) == 1:
595
          # child (and all its descendants) is simple, doesn't split
596
          # over multiple hosts, so we don't need to describe it, our
597
          # own entry for this node describes it completely
598
          continue
599
        else:
600
          # check if child nodes differ from my nodes; note that
601
          # subdisk can differ from the child itself, and be instead
602
          # one of its descendants
603
          for subnode, subdisk in child_result:
604
            if subnode not in my_nodes:
605
              result.append((subnode, subdisk))
606
            # otherwise child is under our own node, so we ignore this
607
            # entry (but probably the other results in the list will
608
            # be different)
609
    return result
610

    
611
  def ComputeGrowth(self, amount):
612
    """Compute the per-VG growth requirements.
613

614
    This only works for VG-based disks.
615

616
    @type amount: integer
617
    @param amount: the desired increase in (user-visible) disk space
618
    @rtype: dict
619
    @return: a dictionary of volume-groups and the required size
620

621
    """
622
    if self.dev_type == constants.LD_LV:
623
      return {self.logical_id[0]: amount}
624
    elif self.dev_type == constants.LD_DRBD8:
625
      if self.children:
626
        return self.children[0].ComputeGrowth(amount)
627
      else:
628
        return {}
629
    else:
630
      # Other disk types do not require VG space
631
      return {}
632

    
633
  def RecordGrow(self, amount):
634
    """Update the size of this disk after growth.
635

636
    This method recurses over the disks's children and updates their
637
    size correspondigly. The method needs to be kept in sync with the
638
    actual algorithms from bdev.
639

640
    """
641
    if self.dev_type in (constants.LD_LV, constants.LD_FILE,
642
                         constants.LD_RBD, constants.LD_EXT):
643
      self.size += amount
644
    elif self.dev_type == constants.LD_DRBD8:
645
      if self.children:
646
        self.children[0].RecordGrow(amount)
647
      self.size += amount
648
    else:
649
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
650
                                   " disk type %s" % self.dev_type)
651

    
652
  def Update(self, size=None, mode=None):
653
    """Apply changes to size and mode.
654

655
    """
656
    if self.dev_type == constants.LD_DRBD8:
657
      if self.children:
658
        self.children[0].Update(size=size, mode=mode)
659
    else:
660
      assert not self.children
661

    
662
    if size is not None:
663
      self.size = size
664
    if mode is not None:
665
      self.mode = mode
666

    
667
  def UnsetSize(self):
668
    """Sets recursively the size to zero for the disk and its children.
669

670
    """
671
    if self.children:
672
      for child in self.children:
673
        child.UnsetSize()
674
    self.size = 0
675

    
676
  def SetPhysicalID(self, target_node, nodes_ip):
677
    """Convert the logical ID to the physical ID.
678

679
    This is used only for drbd, which needs ip/port configuration.
680

681
    The routine descends down and updates its children also, because
682
    this helps when the only the top device is passed to the remote
683
    node.
684

685
    Arguments:
686
      - target_node: the node we wish to configure for
687
      - nodes_ip: a mapping of node name to ip
688

689
    The target_node must exist in in nodes_ip, and must be one of the
690
    nodes in the logical ID for each of the DRBD devices encountered
691
    in the disk tree.
692

693
    """
694
    if self.children:
695
      for child in self.children:
696
        child.SetPhysicalID(target_node, nodes_ip)
697

    
698
    if self.logical_id is None and self.physical_id is not None:
699
      return
700
    if self.dev_type in constants.LDS_DRBD:
701
      pnode, snode, port, pminor, sminor, secret = self.logical_id
702
      if target_node not in (pnode, snode):
703
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
704
                                        target_node)
705
      pnode_ip = nodes_ip.get(pnode, None)
706
      snode_ip = nodes_ip.get(snode, None)
707
      if pnode_ip is None or snode_ip is None:
708
        raise errors.ConfigurationError("Can't find primary or secondary node"
709
                                        " for %s" % str(self))
710
      p_data = (pnode_ip, port)
711
      s_data = (snode_ip, port)
712
      if pnode == target_node:
713
        self.physical_id = p_data + s_data + (pminor, secret)
714
      else: # it must be secondary, we tested above
715
        self.physical_id = s_data + p_data + (sminor, secret)
716
    else:
717
      self.physical_id = self.logical_id
718
    return
719

    
720
  def ToDict(self):
721
    """Disk-specific conversion to standard python types.
722

723
    This replaces the children lists of objects with lists of
724
    standard python types.
725

726
    """
727
    bo = super(Disk, self).ToDict()
728

    
729
    for attr in ("children",):
730
      alist = bo.get(attr, None)
731
      if alist:
732
        bo[attr] = objectutils.ContainerToDicts(alist)
733
    return bo
734

    
735
  @classmethod
736
  def FromDict(cls, val):
737
    """Custom function for Disks
738

739
    """
740
    obj = super(Disk, cls).FromDict(val)
741
    if obj.children:
742
      obj.children = objectutils.ContainerFromDicts(obj.children, list, Disk)
743
    if obj.logical_id and isinstance(obj.logical_id, list):
744
      obj.logical_id = tuple(obj.logical_id)
745
    if obj.physical_id and isinstance(obj.physical_id, list):
746
      obj.physical_id = tuple(obj.physical_id)
747
    if obj.dev_type in constants.LDS_DRBD:
748
      # we need a tuple of length six here
749
      if len(obj.logical_id) < 6:
750
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
751
    return obj
752

    
753
  def __str__(self):
754
    """Custom str() formatter for disks.
755

756
    """
757
    if self.dev_type == constants.LD_LV:
758
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
759
    elif self.dev_type in constants.LDS_DRBD:
760
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
761
      val = "<DRBD8("
762
      if self.physical_id is None:
763
        phy = "unconfigured"
764
      else:
765
        phy = ("configured as %s:%s %s:%s" %
766
               (self.physical_id[0], self.physical_id[1],
767
                self.physical_id[2], self.physical_id[3]))
768

    
769
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
770
              (node_a, minor_a, node_b, minor_b, port, phy))
771
      if self.children and self.children.count(None) == 0:
772
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
773
      else:
774
        val += "no local storage"
775
    else:
776
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
777
             (self.dev_type, self.logical_id, self.physical_id, self.children))
778
    if self.iv_name is None:
779
      val += ", not visible"
780
    else:
781
      val += ", visible as /dev/%s" % self.iv_name
782
    if isinstance(self.size, int):
783
      val += ", size=%dm)>" % self.size
784
    else:
785
      val += ", size='%s')>" % (self.size,)
786
    return val
787

    
788
  def Verify(self):
789
    """Checks that this disk is correctly configured.
790

791
    """
792
    all_errors = []
793
    if self.mode not in constants.DISK_ACCESS_SET:
794
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
795
    return all_errors
796

    
797
  def UpgradeConfig(self):
798
    """Fill defaults for missing configuration values.
799

800
    """
801
    if self.children:
802
      for child in self.children:
803
        child.UpgradeConfig()
804

    
805
    # FIXME: Make this configurable in Ganeti 2.7
806
    self.params = {}
807
    # add here config upgrade for this disk
808

    
809
  @staticmethod
810
  def ComputeLDParams(disk_template, disk_params):
811
    """Computes Logical Disk parameters from Disk Template parameters.
812

813
    @type disk_template: string
814
    @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
815
    @type disk_params: dict
816
    @param disk_params: disk template parameters;
817
                        dict(template_name -> parameters
818
    @rtype: list(dict)
819
    @return: a list of dicts, one for each node of the disk hierarchy. Each dict
820
      contains the LD parameters of the node. The tree is flattened in-order.
821

822
    """
823
    if disk_template not in constants.DISK_TEMPLATES:
824
      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
825

    
826
    assert disk_template in disk_params
827

    
828
    result = list()
829
    dt_params = disk_params[disk_template]
830
    if disk_template == constants.DT_DRBD8:
831
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8], {
832
        constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
833
        constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
834
        constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
835
        constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
836
        constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
837
        constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
838
        constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
839
        constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
840
        constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
841
        constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
842
        constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
843
        constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
844
        }))
845

    
846
      # data LV
847
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
848
        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
849
        }))
850

    
851
      # metadata LV
852
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
853
        constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
854
        }))
855

    
856
    elif disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
857
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
858

    
859
    elif disk_template == constants.DT_PLAIN:
860
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
861
        constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
862
        }))
863

    
864
    elif disk_template == constants.DT_BLOCK:
865
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
866

    
867
    elif disk_template == constants.DT_RBD:
868
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], {
869
        constants.LDP_POOL: dt_params[constants.RBD_POOL],
870
        }))
871

    
872
    elif disk_template == constants.DT_EXT:
873
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_EXT])
874

    
875
    return result
876

    
877

    
878
class InstancePolicy(ConfigObject):
879
  """Config object representing instance policy limits dictionary.
880

881

882
  Note that this object is not actually used in the config, it's just
883
  used as a placeholder for a few functions.
884

885
  """
886
  @classmethod
887
  def CheckParameterSyntax(cls, ipolicy, check_std):
888
    """ Check the instance policy for validity.
889

890
    """
891
    for param in constants.ISPECS_PARAMETERS:
892
      InstancePolicy.CheckISpecSyntax(ipolicy, param, check_std)
893
    if constants.IPOLICY_DTS in ipolicy:
894
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
895
    for key in constants.IPOLICY_PARAMETERS:
896
      if key in ipolicy:
897
        InstancePolicy.CheckParameter(key, ipolicy[key])
898
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
899
    if wrong_keys:
900
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
901
                                      utils.CommaJoin(wrong_keys))
902

    
903
  @classmethod
904
  def CheckISpecSyntax(cls, ipolicy, name, check_std):
905
    """Check the instance policy for validity on a given key.
906

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

910
    @type ipolicy: dict
911
    @param ipolicy: dictionary with min, max, std specs
912
    @type name: string
913
    @param name: what are the limits for
914
    @type check_std: bool
915
    @param check_std: Whether to check std value or just assume compliance
916
    @raise errors.ConfigureError: when specs for given name are not valid
917

918
    """
919
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
920

    
921
    if check_std:
922
      std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
923
      std_msg = std_v
924
    else:
925
      std_v = min_v
926
      std_msg = "-"
927

    
928
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
929
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
930
           (name,
931
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
932
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
933
            std_msg))
934
    if min_v > std_v or std_v > max_v:
935
      raise errors.ConfigurationError(err)
936

    
937
  @classmethod
938
  def CheckDiskTemplates(cls, disk_templates):
939
    """Checks the disk templates for validity.
940

941
    """
942
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
943
    if wrong:
944
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
945
                                      utils.CommaJoin(wrong))
946

    
947
  @classmethod
948
  def CheckParameter(cls, key, value):
949
    """Checks a parameter.
950

951
    Currently we expect all parameters to be float values.
952

953
    """
954
    try:
955
      float(value)
956
    except (TypeError, ValueError), err:
957
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
958
                                      " '%s', error: %s" % (key, value, err))
959

    
960

    
961
class Instance(TaggableObject):
962
  """Config object representing an instance."""
963
  __slots__ = [
964
    "name",
965
    "primary_node",
966
    "os",
967
    "hypervisor",
968
    "hvparams",
969
    "beparams",
970
    "osparams",
971
    "admin_state",
972
    "nics",
973
    "disks",
974
    "disk_template",
975
    "network_port",
976
    "serial_no",
977
    ] + _TIMESTAMPS + _UUID
978

    
979
  def _ComputeSecondaryNodes(self):
980
    """Compute the list of secondary nodes.
981

982
    This is a simple wrapper over _ComputeAllNodes.
983

984
    """
985
    all_nodes = set(self._ComputeAllNodes())
986
    all_nodes.discard(self.primary_node)
987
    return tuple(all_nodes)
988

    
989
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
990
                             "List of names of secondary nodes")
991

    
992
  def _ComputeAllNodes(self):
993
    """Compute the list of all nodes.
994

995
    Since the data is already there (in the drbd disks), keeping it as
996
    a separate normal attribute is redundant and if not properly
997
    synchronised can cause problems. Thus it's better to compute it
998
    dynamically.
999

1000
    """
1001
    def _Helper(nodes, device):
1002
      """Recursively computes nodes given a top device."""
1003
      if device.dev_type in constants.LDS_DRBD:
1004
        nodea, nodeb = device.logical_id[:2]
1005
        nodes.add(nodea)
1006
        nodes.add(nodeb)
1007
      if device.children:
1008
        for child in device.children:
1009
          _Helper(nodes, child)
1010

    
1011
    all_nodes = set()
1012
    all_nodes.add(self.primary_node)
1013
    for device in self.disks:
1014
      _Helper(all_nodes, device)
1015
    return tuple(all_nodes)
1016

    
1017
  all_nodes = property(_ComputeAllNodes, None, None,
1018
                       "List of names of all the nodes of the instance")
1019

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

1023
    This function figures out what logical volumes should belong on
1024
    which nodes, recursing through a device tree.
1025

1026
    @param lvmap: optional dictionary to receive the
1027
        'node' : ['lv', ...] data.
1028

1029
    @return: None if lvmap arg is given, otherwise, a dictionary of
1030
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1031
        volumeN is of the form "vg_name/lv_name", compatible with
1032
        GetVolumeList()
1033

1034
    """
1035
    if node is None:
1036
      node = self.primary_node
1037

    
1038
    if lvmap is None:
1039
      lvmap = {
1040
        node: [],
1041
        }
1042
      ret = lvmap
1043
    else:
1044
      if not node in lvmap:
1045
        lvmap[node] = []
1046
      ret = None
1047

    
1048
    if not devs:
1049
      devs = self.disks
1050

    
1051
    for dev in devs:
1052
      if dev.dev_type == constants.LD_LV:
1053
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1054

    
1055
      elif dev.dev_type in constants.LDS_DRBD:
1056
        if dev.children:
1057
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1058
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1059

    
1060
      elif dev.children:
1061
        self.MapLVsByNode(lvmap, dev.children, node)
1062

    
1063
    return ret
1064

    
1065
  def FindDisk(self, idx):
1066
    """Find a disk given having a specified index.
1067

1068
    This is just a wrapper that does validation of the index.
1069

1070
    @type idx: int
1071
    @param idx: the disk index
1072
    @rtype: L{Disk}
1073
    @return: the corresponding disk
1074
    @raise errors.OpPrereqError: when the given index is not valid
1075

1076
    """
1077
    try:
1078
      idx = int(idx)
1079
      return self.disks[idx]
1080
    except (TypeError, ValueError), err:
1081
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1082
                                 errors.ECODE_INVAL)
1083
    except IndexError:
1084
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1085
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1086
                                 errors.ECODE_INVAL)
1087

    
1088
  def ToDict(self):
1089
    """Instance-specific conversion to standard python types.
1090

1091
    This replaces the children lists of objects with lists of standard
1092
    python types.
1093

1094
    """
1095
    bo = super(Instance, self).ToDict()
1096

    
1097
    for attr in "nics", "disks":
1098
      alist = bo.get(attr, None)
1099
      if alist:
1100
        nlist = objectutils.ContainerToDicts(alist)
1101
      else:
1102
        nlist = []
1103
      bo[attr] = nlist
1104
    return bo
1105

    
1106
  @classmethod
1107
  def FromDict(cls, val):
1108
    """Custom function for instances.
1109

1110
    """
1111
    if "admin_state" not in val:
1112
      if val.get("admin_up", False):
1113
        val["admin_state"] = constants.ADMINST_UP
1114
      else:
1115
        val["admin_state"] = constants.ADMINST_DOWN
1116
    if "admin_up" in val:
1117
      del val["admin_up"]
1118
    obj = super(Instance, cls).FromDict(val)
1119
    obj.nics = objectutils.ContainerFromDicts(obj.nics, list, NIC)
1120
    obj.disks = objectutils.ContainerFromDicts(obj.disks, list, Disk)
1121
    return obj
1122

    
1123
  def UpgradeConfig(self):
1124
    """Fill defaults for missing configuration values.
1125

1126
    """
1127
    for nic in self.nics:
1128
      nic.UpgradeConfig()
1129
    for disk in self.disks:
1130
      disk.UpgradeConfig()
1131
    if self.hvparams:
1132
      for key in constants.HVC_GLOBALS:
1133
        try:
1134
          del self.hvparams[key]
1135
        except KeyError:
1136
          pass
1137
    if self.osparams is None:
1138
      self.osparams = {}
1139
    UpgradeBeParams(self.beparams)
1140

    
1141

    
1142
class OS(ConfigObject):
1143
  """Config object representing an operating system.
1144

1145
  @type supported_parameters: list
1146
  @ivar supported_parameters: a list of tuples, name and description,
1147
      containing the supported parameters by this OS
1148

1149
  @type VARIANT_DELIM: string
1150
  @cvar VARIANT_DELIM: the variant delimiter
1151

1152
  """
1153
  __slots__ = [
1154
    "name",
1155
    "path",
1156
    "api_versions",
1157
    "create_script",
1158
    "export_script",
1159
    "import_script",
1160
    "rename_script",
1161
    "verify_script",
1162
    "supported_variants",
1163
    "supported_parameters",
1164
    ]
1165

    
1166
  VARIANT_DELIM = "+"
1167

    
1168
  @classmethod
1169
  def SplitNameVariant(cls, name):
1170
    """Splits the name into the proper name and variant.
1171

1172
    @param name: the OS (unprocessed) name
1173
    @rtype: list
1174
    @return: a list of two elements; if the original name didn't
1175
        contain a variant, it's returned as an empty string
1176

1177
    """
1178
    nv = name.split(cls.VARIANT_DELIM, 1)
1179
    if len(nv) == 1:
1180
      nv.append("")
1181
    return nv
1182

    
1183
  @classmethod
1184
  def GetName(cls, name):
1185
    """Returns the proper name of the os (without the variant).
1186

1187
    @param name: the OS (unprocessed) name
1188

1189
    """
1190
    return cls.SplitNameVariant(name)[0]
1191

    
1192
  @classmethod
1193
  def GetVariant(cls, name):
1194
    """Returns the variant the os (without the base name).
1195

1196
    @param name: the OS (unprocessed) name
1197

1198
    """
1199
    return cls.SplitNameVariant(name)[1]
1200

    
1201

    
1202
class ExtStorage(ConfigObject):
1203
  """Config object representing an External Storage Provider.
1204

1205
  """
1206
  __slots__ = [
1207
    "name",
1208
    "path",
1209
    "create_script",
1210
    "remove_script",
1211
    "grow_script",
1212
    "attach_script",
1213
    "detach_script",
1214
    "setinfo_script",
1215
    "verify_script",
1216
    "supported_parameters",
1217
    ]
1218

    
1219

    
1220
class NodeHvState(ConfigObject):
1221
  """Hypvervisor state on a node.
1222

1223
  @ivar mem_total: Total amount of memory
1224
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1225
    available)
1226
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1227
    rounding
1228
  @ivar mem_inst: Memory used by instances living on node
1229
  @ivar cpu_total: Total node CPU core count
1230
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1231

1232
  """
1233
  __slots__ = [
1234
    "mem_total",
1235
    "mem_node",
1236
    "mem_hv",
1237
    "mem_inst",
1238
    "cpu_total",
1239
    "cpu_node",
1240
    ] + _TIMESTAMPS
1241

    
1242

    
1243
class NodeDiskState(ConfigObject):
1244
  """Disk state on a node.
1245

1246
  """
1247
  __slots__ = [
1248
    "total",
1249
    "reserved",
1250
    "overhead",
1251
    ] + _TIMESTAMPS
1252

    
1253

    
1254
class Node(TaggableObject):
1255
  """Config object representing a node.
1256

1257
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1258
  @ivar hv_state_static: Hypervisor state overriden by user
1259
  @ivar disk_state: Disk state (e.g. free space)
1260
  @ivar disk_state_static: Disk state overriden by user
1261

1262
  """
1263
  __slots__ = [
1264
    "name",
1265
    "primary_ip",
1266
    "secondary_ip",
1267
    "serial_no",
1268
    "master_candidate",
1269
    "offline",
1270
    "drained",
1271
    "group",
1272
    "master_capable",
1273
    "vm_capable",
1274
    "ndparams",
1275
    "powered",
1276
    "hv_state",
1277
    "hv_state_static",
1278
    "disk_state",
1279
    "disk_state_static",
1280
    ] + _TIMESTAMPS + _UUID
1281

    
1282
  def UpgradeConfig(self):
1283
    """Fill defaults for missing configuration values.
1284

1285
    """
1286
    # pylint: disable=E0203
1287
    # because these are "defined" via slots, not manually
1288
    if self.master_capable is None:
1289
      self.master_capable = True
1290

    
1291
    if self.vm_capable is None:
1292
      self.vm_capable = True
1293

    
1294
    if self.ndparams is None:
1295
      self.ndparams = {}
1296

    
1297
    if self.powered is None:
1298
      self.powered = True
1299

    
1300
  def ToDict(self):
1301
    """Custom function for serializing.
1302

1303
    """
1304
    data = super(Node, self).ToDict()
1305

    
1306
    hv_state = data.get("hv_state", None)
1307
    if hv_state is not None:
1308
      data["hv_state"] = objectutils.ContainerToDicts(hv_state)
1309

    
1310
    disk_state = data.get("disk_state", None)
1311
    if disk_state is not None:
1312
      data["disk_state"] = \
1313
        dict((key, objectutils.ContainerToDicts(value))
1314
             for (key, value) in disk_state.items())
1315

    
1316
    return data
1317

    
1318
  @classmethod
1319
  def FromDict(cls, val):
1320
    """Custom function for deserializing.
1321

1322
    """
1323
    obj = super(Node, cls).FromDict(val)
1324

    
1325
    if obj.hv_state is not None:
1326
      obj.hv_state = \
1327
        objectutils.ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1328

    
1329
    if obj.disk_state is not None:
1330
      obj.disk_state = \
1331
        dict((key, objectutils.ContainerFromDicts(value, dict, NodeDiskState))
1332
             for (key, value) in obj.disk_state.items())
1333

    
1334
    return obj
1335

    
1336

    
1337
class NodeGroup(TaggableObject):
1338
  """Config object representing a node group."""
1339
  __slots__ = [
1340
    "name",
1341
    "members",
1342
    "ndparams",
1343
    "diskparams",
1344
    "ipolicy",
1345
    "serial_no",
1346
    "hv_state_static",
1347
    "disk_state_static",
1348
    "alloc_policy",
1349
    "networks",
1350
    ] + _TIMESTAMPS + _UUID
1351

    
1352
  def ToDict(self):
1353
    """Custom function for nodegroup.
1354

1355
    This discards the members object, which gets recalculated and is only kept
1356
    in memory.
1357

1358
    """
1359
    mydict = super(NodeGroup, self).ToDict()
1360
    del mydict["members"]
1361
    return mydict
1362

    
1363
  @classmethod
1364
  def FromDict(cls, val):
1365
    """Custom function for nodegroup.
1366

1367
    The members slot is initialized to an empty list, upon deserialization.
1368

1369
    """
1370
    obj = super(NodeGroup, cls).FromDict(val)
1371
    obj.members = []
1372
    return obj
1373

    
1374
  def UpgradeConfig(self):
1375
    """Fill defaults for missing configuration values.
1376

1377
    """
1378
    if self.ndparams is None:
1379
      self.ndparams = {}
1380

    
1381
    if self.serial_no is None:
1382
      self.serial_no = 1
1383

    
1384
    if self.alloc_policy is None:
1385
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1386

    
1387
    # We only update mtime, and not ctime, since we would not be able
1388
    # to provide a correct value for creation time.
1389
    if self.mtime is None:
1390
      self.mtime = time.time()
1391

    
1392
    if self.diskparams is None:
1393
      self.diskparams = {}
1394
    if self.ipolicy is None:
1395
      self.ipolicy = MakeEmptyIPolicy()
1396

    
1397
    if self.networks is None:
1398
      self.networks = {}
1399

    
1400
  def FillND(self, node):
1401
    """Return filled out ndparams for L{objects.Node}
1402

1403
    @type node: L{objects.Node}
1404
    @param node: A Node object to fill
1405
    @return a copy of the node's ndparams with defaults filled
1406

1407
    """
1408
    return self.SimpleFillND(node.ndparams)
1409

    
1410
  def SimpleFillND(self, ndparams):
1411
    """Fill a given ndparams dict with defaults.
1412

1413
    @type ndparams: dict
1414
    @param ndparams: the dict to fill
1415
    @rtype: dict
1416
    @return: a copy of the passed in ndparams with missing keys filled
1417
        from the node group defaults
1418

1419
    """
1420
    return FillDict(self.ndparams, ndparams)
1421

    
1422

    
1423
class Cluster(TaggableObject):
1424
  """Config object representing the cluster."""
1425
  __slots__ = [
1426
    "serial_no",
1427
    "rsahostkeypub",
1428
    "highest_used_port",
1429
    "tcpudp_port_pool",
1430
    "mac_prefix",
1431
    "volume_group_name",
1432
    "reserved_lvs",
1433
    "drbd_usermode_helper",
1434
    "default_bridge",
1435
    "default_hypervisor",
1436
    "master_node",
1437
    "master_ip",
1438
    "master_netdev",
1439
    "master_netmask",
1440
    "use_external_mip_script",
1441
    "cluster_name",
1442
    "file_storage_dir",
1443
    "shared_file_storage_dir",
1444
    "enabled_hypervisors",
1445
    "hvparams",
1446
    "ipolicy",
1447
    "os_hvp",
1448
    "beparams",
1449
    "osparams",
1450
    "nicparams",
1451
    "ndparams",
1452
    "diskparams",
1453
    "candidate_pool_size",
1454
    "modify_etc_hosts",
1455
    "modify_ssh_setup",
1456
    "maintain_node_health",
1457
    "uid_pool",
1458
    "default_iallocator",
1459
    "hidden_os",
1460
    "blacklisted_os",
1461
    "primary_ip_family",
1462
    "prealloc_wipe_disks",
1463
    "hv_state_static",
1464
    "disk_state_static",
1465
    ] + _TIMESTAMPS + _UUID
1466

    
1467
  def UpgradeConfig(self):
1468
    """Fill defaults for missing configuration values.
1469

1470
    """
1471
    # pylint: disable=E0203
1472
    # because these are "defined" via slots, not manually
1473
    if self.hvparams is None:
1474
      self.hvparams = constants.HVC_DEFAULTS
1475
    else:
1476
      for hypervisor in self.hvparams:
1477
        self.hvparams[hypervisor] = FillDict(
1478
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1479

    
1480
    if self.os_hvp is None:
1481
      self.os_hvp = {}
1482

    
1483
    # osparams added before 2.2
1484
    if self.osparams is None:
1485
      self.osparams = {}
1486

    
1487
    self.ndparams = UpgradeNDParams(self.ndparams)
1488

    
1489
    self.beparams = UpgradeGroupedParams(self.beparams,
1490
                                         constants.BEC_DEFAULTS)
1491
    for beparams_group in self.beparams:
1492
      UpgradeBeParams(self.beparams[beparams_group])
1493

    
1494
    migrate_default_bridge = not self.nicparams
1495
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1496
                                          constants.NICC_DEFAULTS)
1497
    if migrate_default_bridge:
1498
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1499
        self.default_bridge
1500

    
1501
    if self.modify_etc_hosts is None:
1502
      self.modify_etc_hosts = True
1503

    
1504
    if self.modify_ssh_setup is None:
1505
      self.modify_ssh_setup = True
1506

    
1507
    # default_bridge is no longer used in 2.1. The slot is left there to
1508
    # support auto-upgrading. It can be removed once we decide to deprecate
1509
    # upgrading straight from 2.0.
1510
    if self.default_bridge is not None:
1511
      self.default_bridge = None
1512

    
1513
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1514
    # code can be removed once upgrading straight from 2.0 is deprecated.
1515
    if self.default_hypervisor is not None:
1516
      self.enabled_hypervisors = ([self.default_hypervisor] +
1517
                                  [hvname for hvname in self.enabled_hypervisors
1518
                                   if hvname != self.default_hypervisor])
1519
      self.default_hypervisor = None
1520

    
1521
    # maintain_node_health added after 2.1.1
1522
    if self.maintain_node_health is None:
1523
      self.maintain_node_health = False
1524

    
1525
    if self.uid_pool is None:
1526
      self.uid_pool = []
1527

    
1528
    if self.default_iallocator is None:
1529
      self.default_iallocator = ""
1530

    
1531
    # reserved_lvs added before 2.2
1532
    if self.reserved_lvs is None:
1533
      self.reserved_lvs = []
1534

    
1535
    # hidden and blacklisted operating systems added before 2.2.1
1536
    if self.hidden_os is None:
1537
      self.hidden_os = []
1538

    
1539
    if self.blacklisted_os is None:
1540
      self.blacklisted_os = []
1541

    
1542
    # primary_ip_family added before 2.3
1543
    if self.primary_ip_family is None:
1544
      self.primary_ip_family = AF_INET
1545

    
1546
    if self.master_netmask is None:
1547
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1548
      self.master_netmask = ipcls.iplen
1549

    
1550
    if self.prealloc_wipe_disks is None:
1551
      self.prealloc_wipe_disks = False
1552

    
1553
    # shared_file_storage_dir added before 2.5
1554
    if self.shared_file_storage_dir is None:
1555
      self.shared_file_storage_dir = ""
1556

    
1557
    if self.use_external_mip_script is None:
1558
      self.use_external_mip_script = False
1559

    
1560
    if self.diskparams:
1561
      self.diskparams = UpgradeDiskParams(self.diskparams)
1562
    else:
1563
      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1564

    
1565
    # instance policy added before 2.6
1566
    if self.ipolicy is None:
1567
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1568
    else:
1569
      # we can either make sure to upgrade the ipolicy always, or only
1570
      # do it in some corner cases (e.g. missing keys); note that this
1571
      # will break any removal of keys from the ipolicy dict
1572
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1573

    
1574
  @property
1575
  def primary_hypervisor(self):
1576
    """The first hypervisor is the primary.
1577

1578
    Useful, for example, for L{Node}'s hv/disk state.
1579

1580
    """
1581
    return self.enabled_hypervisors[0]
1582

    
1583
  def ToDict(self):
1584
    """Custom function for cluster.
1585

1586
    """
1587
    mydict = super(Cluster, self).ToDict()
1588

    
1589
    if self.tcpudp_port_pool is None:
1590
      tcpudp_port_pool = []
1591
    else:
1592
      tcpudp_port_pool = list(self.tcpudp_port_pool)
1593

    
1594
    mydict["tcpudp_port_pool"] = tcpudp_port_pool
1595

    
1596
    return mydict
1597

    
1598
  @classmethod
1599
  def FromDict(cls, val):
1600
    """Custom function for cluster.
1601

1602
    """
1603
    obj = super(Cluster, cls).FromDict(val)
1604

    
1605
    if obj.tcpudp_port_pool is None:
1606
      obj.tcpudp_port_pool = set()
1607
    elif not isinstance(obj.tcpudp_port_pool, set):
1608
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1609

    
1610
    return obj
1611

    
1612
  def SimpleFillDP(self, diskparams):
1613
    """Fill a given diskparams dict with cluster defaults.
1614

1615
    @param diskparams: The diskparams
1616
    @return: The defaults dict
1617

1618
    """
1619
    return FillDiskParams(self.diskparams, diskparams)
1620

    
1621
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1622
    """Get the default hypervisor parameters for the cluster.
1623

1624
    @param hypervisor: the hypervisor name
1625
    @param os_name: if specified, we'll also update the defaults for this OS
1626
    @param skip_keys: if passed, list of keys not to use
1627
    @return: the defaults dict
1628

1629
    """
1630
    if skip_keys is None:
1631
      skip_keys = []
1632

    
1633
    fill_stack = [self.hvparams.get(hypervisor, {})]
1634
    if os_name is not None:
1635
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1636
      fill_stack.append(os_hvp)
1637

    
1638
    ret_dict = {}
1639
    for o_dict in fill_stack:
1640
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1641

    
1642
    return ret_dict
1643

    
1644
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1645
    """Fill a given hvparams dict with cluster defaults.
1646

1647
    @type hv_name: string
1648
    @param hv_name: the hypervisor to use
1649
    @type os_name: string
1650
    @param os_name: the OS to use for overriding the hypervisor defaults
1651
    @type skip_globals: boolean
1652
    @param skip_globals: if True, the global hypervisor parameters will
1653
        not be filled
1654
    @rtype: dict
1655
    @return: a copy of the given hvparams with missing keys filled from
1656
        the cluster defaults
1657

1658
    """
1659
    if skip_globals:
1660
      skip_keys = constants.HVC_GLOBALS
1661
    else:
1662
      skip_keys = []
1663

    
1664
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1665
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1666

    
1667
  def FillHV(self, instance, skip_globals=False):
1668
    """Fill an instance's hvparams dict with cluster defaults.
1669

1670
    @type instance: L{objects.Instance}
1671
    @param instance: the instance parameter to fill
1672
    @type skip_globals: boolean
1673
    @param skip_globals: if True, the global hypervisor parameters will
1674
        not be filled
1675
    @rtype: dict
1676
    @return: a copy of the instance's hvparams with missing keys filled from
1677
        the cluster defaults
1678

1679
    """
1680
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1681
                             instance.hvparams, skip_globals)
1682

    
1683
  def SimpleFillBE(self, beparams):
1684
    """Fill a given beparams dict with cluster defaults.
1685

1686
    @type beparams: dict
1687
    @param beparams: the dict to fill
1688
    @rtype: dict
1689
    @return: a copy of the passed in beparams with missing keys filled
1690
        from the cluster defaults
1691

1692
    """
1693
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1694

    
1695
  def FillBE(self, instance):
1696
    """Fill an instance's beparams dict with cluster defaults.
1697

1698
    @type instance: L{objects.Instance}
1699
    @param instance: the instance parameter to fill
1700
    @rtype: dict
1701
    @return: a copy of the instance's beparams with missing keys filled from
1702
        the cluster defaults
1703

1704
    """
1705
    return self.SimpleFillBE(instance.beparams)
1706

    
1707
  def SimpleFillNIC(self, nicparams):
1708
    """Fill a given nicparams dict with cluster defaults.
1709

1710
    @type nicparams: dict
1711
    @param nicparams: the dict to fill
1712
    @rtype: dict
1713
    @return: a copy of the passed in nicparams with missing keys filled
1714
        from the cluster defaults
1715

1716
    """
1717
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1718

    
1719
  def SimpleFillOS(self, os_name, os_params):
1720
    """Fill an instance's osparams dict with cluster defaults.
1721

1722
    @type os_name: string
1723
    @param os_name: the OS name to use
1724
    @type os_params: dict
1725
    @param os_params: the dict to fill with default values
1726
    @rtype: dict
1727
    @return: a copy of the instance's osparams with missing keys filled from
1728
        the cluster defaults
1729

1730
    """
1731
    name_only = os_name.split("+", 1)[0]
1732
    # base OS
1733
    result = self.osparams.get(name_only, {})
1734
    # OS with variant
1735
    result = FillDict(result, self.osparams.get(os_name, {}))
1736
    # specified params
1737
    return FillDict(result, os_params)
1738

    
1739
  @staticmethod
1740
  def SimpleFillHvState(hv_state):
1741
    """Fill an hv_state sub dict with cluster defaults.
1742

1743
    """
1744
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1745

    
1746
  @staticmethod
1747
  def SimpleFillDiskState(disk_state):
1748
    """Fill an disk_state sub dict with cluster defaults.
1749

1750
    """
1751
    return FillDict(constants.DS_DEFAULTS, disk_state)
1752

    
1753
  def FillND(self, node, nodegroup):
1754
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1755

1756
    @type node: L{objects.Node}
1757
    @param node: A Node object to fill
1758
    @type nodegroup: L{objects.NodeGroup}
1759
    @param nodegroup: A Node object to fill
1760
    @return a copy of the node's ndparams with defaults filled
1761

1762
    """
1763
    return self.SimpleFillND(nodegroup.FillND(node))
1764

    
1765
  def SimpleFillND(self, ndparams):
1766
    """Fill a given ndparams dict with defaults.
1767

1768
    @type ndparams: dict
1769
    @param ndparams: the dict to fill
1770
    @rtype: dict
1771
    @return: a copy of the passed in ndparams with missing keys filled
1772
        from the cluster defaults
1773

1774
    """
1775
    return FillDict(self.ndparams, ndparams)
1776

    
1777
  def SimpleFillIPolicy(self, ipolicy):
1778
    """ Fill instance policy dict with defaults.
1779

1780
    @type ipolicy: dict
1781
    @param ipolicy: the dict to fill
1782
    @rtype: dict
1783
    @return: a copy of passed ipolicy with missing keys filled from
1784
      the cluster defaults
1785

1786
    """
1787
    return FillIPolicy(self.ipolicy, ipolicy)
1788

    
1789

    
1790
class BlockDevStatus(ConfigObject):
1791
  """Config object representing the status of a block device."""
1792
  __slots__ = [
1793
    "dev_path",
1794
    "major",
1795
    "minor",
1796
    "sync_percent",
1797
    "estimated_time",
1798
    "is_degraded",
1799
    "ldisk_status",
1800
    ]
1801

    
1802

    
1803
class ImportExportStatus(ConfigObject):
1804
  """Config object representing the status of an import or export."""
1805
  __slots__ = [
1806
    "recent_output",
1807
    "listen_port",
1808
    "connected",
1809
    "progress_mbytes",
1810
    "progress_throughput",
1811
    "progress_eta",
1812
    "progress_percent",
1813
    "exit_status",
1814
    "error_message",
1815
    ] + _TIMESTAMPS
1816

    
1817

    
1818
class ImportExportOptions(ConfigObject):
1819
  """Options for import/export daemon
1820

1821
  @ivar key_name: X509 key name (None for cluster certificate)
1822
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1823
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1824
  @ivar magic: Used to ensure the connection goes to the right disk
1825
  @ivar ipv6: Whether to use IPv6
1826
  @ivar connect_timeout: Number of seconds for establishing connection
1827

1828
  """
1829
  __slots__ = [
1830
    "key_name",
1831
    "ca_pem",
1832
    "compress",
1833
    "magic",
1834
    "ipv6",
1835
    "connect_timeout",
1836
    ]
1837

    
1838

    
1839
class ConfdRequest(ConfigObject):
1840
  """Object holding a confd request.
1841

1842
  @ivar protocol: confd protocol version
1843
  @ivar type: confd query type
1844
  @ivar query: query request
1845
  @ivar rsalt: requested reply salt
1846

1847
  """
1848
  __slots__ = [
1849
    "protocol",
1850
    "type",
1851
    "query",
1852
    "rsalt",
1853
    ]
1854

    
1855

    
1856
class ConfdReply(ConfigObject):
1857
  """Object holding a confd reply.
1858

1859
  @ivar protocol: confd protocol version
1860
  @ivar status: reply status code (ok, error)
1861
  @ivar answer: confd query reply
1862
  @ivar serial: configuration serial number
1863

1864
  """
1865
  __slots__ = [
1866
    "protocol",
1867
    "status",
1868
    "answer",
1869
    "serial",
1870
    ]
1871

    
1872

    
1873
class QueryFieldDefinition(ConfigObject):
1874
  """Object holding a query field definition.
1875

1876
  @ivar name: Field name
1877
  @ivar title: Human-readable title
1878
  @ivar kind: Field type
1879
  @ivar doc: Human-readable description
1880

1881
  """
1882
  __slots__ = [
1883
    "name",
1884
    "title",
1885
    "kind",
1886
    "doc",
1887
    ]
1888

    
1889

    
1890
class _QueryResponseBase(ConfigObject):
1891
  __slots__ = [
1892
    "fields",
1893
    ]
1894

    
1895
  def ToDict(self):
1896
    """Custom function for serializing.
1897

1898
    """
1899
    mydict = super(_QueryResponseBase, self).ToDict()
1900
    mydict["fields"] = objectutils.ContainerToDicts(mydict["fields"])
1901
    return mydict
1902

    
1903
  @classmethod
1904
  def FromDict(cls, val):
1905
    """Custom function for de-serializing.
1906

1907
    """
1908
    obj = super(_QueryResponseBase, cls).FromDict(val)
1909
    obj.fields = \
1910
      objectutils.ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1911
    return obj
1912

    
1913

    
1914
class QueryResponse(_QueryResponseBase):
1915
  """Object holding the response to a query.
1916

1917
  @ivar fields: List of L{QueryFieldDefinition} objects
1918
  @ivar data: Requested data
1919

1920
  """
1921
  __slots__ = [
1922
    "data",
1923
    ]
1924

    
1925

    
1926
class QueryFieldsRequest(ConfigObject):
1927
  """Object holding a request for querying available fields.
1928

1929
  """
1930
  __slots__ = [
1931
    "what",
1932
    "fields",
1933
    ]
1934

    
1935

    
1936
class QueryFieldsResponse(_QueryResponseBase):
1937
  """Object holding the response to a query for fields.
1938

1939
  @ivar fields: List of L{QueryFieldDefinition} objects
1940

1941
  """
1942
  __slots__ = []
1943

    
1944

    
1945
class MigrationStatus(ConfigObject):
1946
  """Object holding the status of a migration.
1947

1948
  """
1949
  __slots__ = [
1950
    "status",
1951
    "transferred_ram",
1952
    "total_ram",
1953
    ]
1954

    
1955

    
1956
class InstanceConsole(ConfigObject):
1957
  """Object describing how to access the console of an instance.
1958

1959
  """
1960
  __slots__ = [
1961
    "instance",
1962
    "kind",
1963
    "message",
1964
    "host",
1965
    "port",
1966
    "user",
1967
    "command",
1968
    "display",
1969
    ]
1970

    
1971
  def Validate(self):
1972
    """Validates contents of this object.
1973

1974
    """
1975
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1976
    assert self.instance, "Missing instance name"
1977
    assert self.message or self.kind in [constants.CONS_SSH,
1978
                                         constants.CONS_SPICE,
1979
                                         constants.CONS_VNC]
1980
    assert self.host or self.kind == constants.CONS_MESSAGE
1981
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1982
                                      constants.CONS_SSH]
1983
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1984
                                      constants.CONS_SPICE,
1985
                                      constants.CONS_VNC]
1986
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1987
                                         constants.CONS_SPICE,
1988
                                         constants.CONS_VNC]
1989
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1990
                                         constants.CONS_SPICE,
1991
                                         constants.CONS_SSH]
1992
    return True
1993

    
1994

    
1995
class Network(TaggableObject):
1996
  """Object representing a network definition for ganeti.
1997

1998
  """
1999
  __slots__ = [
2000
    "name",
2001
    "serial_no",
2002
    "network_type",
2003
    "mac_prefix",
2004
    "family",
2005
    "network",
2006
    "network6",
2007
    "gateway",
2008
    "gateway6",
2009
    "size",
2010
    "reservations",
2011
    "ext_reservations",
2012
    ] + _TIMESTAMPS + _UUID
2013

    
2014

    
2015
class SerializableConfigParser(ConfigParser.SafeConfigParser):
2016
  """Simple wrapper over ConfigParse that allows serialization.
2017

2018
  This class is basically ConfigParser.SafeConfigParser with two
2019
  additional methods that allow it to serialize/unserialize to/from a
2020
  buffer.
2021

2022
  """
2023
  def Dumps(self):
2024
    """Dump this instance and return the string representation."""
2025
    buf = StringIO()
2026
    self.write(buf)
2027
    return buf.getvalue()
2028

    
2029
  @classmethod
2030
  def Loads(cls, data):
2031
    """Load data from a string."""
2032
    buf = StringIO(data)
2033
    cfp = cls()
2034
    cfp.readfp(buf)
2035
    return cfp
2036

    
2037

    
2038
class LvmPvInfo(ConfigObject):
2039
  """Information about an LVM physical volume (PV).
2040

2041
  @type name: string
2042
  @ivar name: name of the PV
2043
  @type vg_name: string
2044
  @ivar vg_name: name of the volume group containing the PV
2045
  @type size: float
2046
  @ivar size: size of the PV in MiB
2047
  @type free: float
2048
  @ivar free: free space in the PV, in MiB
2049
  @type attributes: string
2050
  @ivar attributes: PV attributes
2051
  @type lv_list: list of strings
2052
  @ivar lv_list: names of the LVs hosted on the PV
2053
  """
2054
  __slots__ = [
2055
    "name",
2056
    "vg_name",
2057
    "size",
2058
    "free",
2059
    "attributes",
2060
    "lv_list"
2061
    ]
2062

    
2063
  def IsEmpty(self):
2064
    """Is this PV empty?
2065

2066
    """
2067
    return self.size <= (self.free + 1)
2068

    
2069
  def IsAllocatable(self):
2070
    """Is this PV allocatable?
2071

2072
    """
2073
    return ("a" in self.attributes)