Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 1a2eb2dc

History | View | Annotate | Download (57.4 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"]
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
    "serial_no",
436
    ] + _TIMESTAMPS
437

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

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

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

    
450
    return mydict
451

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

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

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

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

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

    
479
  def UpgradeConfig(self):
480
    """Fill defaults for missing configuration values.
481

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

    
499

    
500
class NIC(ConfigObject):
501
  """Config object representing a network card."""
502
  __slots__ = ["mac", "ip", "nicparams"]
503

    
504
  @classmethod
505
  def CheckParameterSyntax(cls, nicparams):
506
    """Check the given parameters for validity.
507

508
    @type nicparams:  dict
509
    @param nicparams: dictionary with parameter names/value
510
    @raise errors.ConfigurationError: when a parameter is not valid
511

512
    """
513
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
514
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
515
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
516
      raise errors.ConfigurationError(err)
517

    
518
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
519
        not nicparams[constants.NIC_LINK]):
520
      err = "Missing bridged nic link"
521
      raise errors.ConfigurationError(err)
522

    
523

    
524
class Disk(ConfigObject):
525
  """Config object representing a block device."""
526
  __slots__ = ["dev_type", "logical_id", "physical_id",
527
               "children", "iv_name", "size", "mode", "params"]
528

    
529
  def CreateOnSecondary(self):
530
    """Test if this device needs to be created on a secondary node."""
531
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
532

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

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

    
541
  def StaticDevPath(self):
542
    """Return the device path if this device type has a static one.
543

544
    Some devices (LVM for example) live always at the same /dev/ path,
545
    irrespective of their status. For such devices, we return this
546
    path, for others we return None.
547

548
    @warning: The path returned is not a normalized pathname; callers
549
        should check that it is a valid path.
550

551
    """
552
    if self.dev_type == constants.LD_LV:
553
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
554
    elif self.dev_type == constants.LD_BLOCKDEV:
555
      return self.logical_id[1]
556
    elif self.dev_type == constants.LD_RBD:
557
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
558
    return None
559

    
560
  def ChildrenNeeded(self):
561
    """Compute the needed number of children for activation.
562

563
    This method will return either -1 (all children) or a positive
564
    number denoting the minimum number of children needed for
565
    activation (only mirrored devices will usually return >=0).
566

567
    Currently, only DRBD8 supports diskless activation (therefore we
568
    return 0), for all other we keep the previous semantics and return
569
    -1.
570

571
    """
572
    if self.dev_type == constants.LD_DRBD8:
573
      return 0
574
    return -1
575

    
576
  def IsBasedOnDiskType(self, dev_type):
577
    """Check if the disk or its children are based on the given type.
578

579
    @type dev_type: L{constants.LDS_BLOCK}
580
    @param dev_type: the type to look for
581
    @rtype: boolean
582
    @return: boolean indicating if a device of the given type was found or not
583

584
    """
585
    if self.children:
586
      for child in self.children:
587
        if child.IsBasedOnDiskType(dev_type):
588
          return True
589
    return self.dev_type == dev_type
590

    
591
  def GetNodes(self, node):
592
    """This function returns the nodes this device lives on.
593

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

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

    
611
  def ComputeNodeTree(self, parent_node):
612
    """Compute the node/disk tree for this disk and its children.
613

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

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

    
646
  def ComputeGrowth(self, amount):
647
    """Compute the per-VG growth requirements.
648

649
    This only works for VG-based disks.
650

651
    @type amount: integer
652
    @param amount: the desired increase in (user-visible) disk space
653
    @rtype: dict
654
    @return: a dictionary of volume-groups and the required size
655

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

    
668
  def RecordGrow(self, amount):
669
    """Update the size of this disk after growth.
670

671
    This method recurses over the disks's children and updates their
672
    size correspondigly. The method needs to be kept in sync with the
673
    actual algorithms from bdev.
674

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

    
687
  def Update(self, size=None, mode=None):
688
    """Apply changes to size and mode.
689

690
    """
691
    if self.dev_type == constants.LD_DRBD8:
692
      if self.children:
693
        self.children[0].Update(size=size, mode=mode)
694
    else:
695
      assert not self.children
696

    
697
    if size is not None:
698
      self.size = size
699
    if mode is not None:
700
      self.mode = mode
701

    
702
  def UnsetSize(self):
703
    """Sets recursively the size to zero for the disk and its children.
704

705
    """
706
    if self.children:
707
      for child in self.children:
708
        child.UnsetSize()
709
    self.size = 0
710

    
711
  def SetPhysicalID(self, target_node, nodes_ip):
712
    """Convert the logical ID to the physical ID.
713

714
    This is used only for drbd, which needs ip/port configuration.
715

716
    The routine descends down and updates its children also, because
717
    this helps when the only the top device is passed to the remote
718
    node.
719

720
    Arguments:
721
      - target_node: the node we wish to configure for
722
      - nodes_ip: a mapping of node name to ip
723

724
    The target_node must exist in in nodes_ip, and must be one of the
725
    nodes in the logical ID for each of the DRBD devices encountered
726
    in the disk tree.
727

728
    """
729
    if self.children:
730
      for child in self.children:
731
        child.SetPhysicalID(target_node, nodes_ip)
732

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

    
755
  def ToDict(self):
756
    """Disk-specific conversion to standard python types.
757

758
    This replaces the children lists of objects with lists of
759
    standard python types.
760

761
    """
762
    bo = super(Disk, self).ToDict()
763

    
764
    for attr in ("children",):
765
      alist = bo.get(attr, None)
766
      if alist:
767
        bo[attr] = self._ContainerToDicts(alist)
768
    return bo
769

    
770
  @classmethod
771
  def FromDict(cls, val):
772
    """Custom function for Disks
773

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

    
788
  def __str__(self):
789
    """Custom str() formatter for disks.
790

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

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

    
823
  def Verify(self):
824
    """Checks that this disk is correctly configured.
825

826
    """
827
    all_errors = []
828
    if self.mode not in constants.DISK_ACCESS_SET:
829
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
830
    return all_errors
831

    
832
  def UpgradeConfig(self):
833
    """Fill defaults for missing configuration values.
834

835
    """
836
    if self.children:
837
      for child in self.children:
838
        child.UpgradeConfig()
839

    
840
    # FIXME: Make this configurable in Ganeti 2.7
841
    self.params = {}
842
    # add here config upgrade for this disk
843

    
844
  @staticmethod
845
  def ComputeLDParams(disk_template, disk_params):
846
    """Computes Logical Disk parameters from Disk Template parameters.
847

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

857
    """
858
    if disk_template not in constants.DISK_TEMPLATES:
859
      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
860

    
861
    assert disk_template in disk_params
862

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

    
881
      # data LV
882
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
883
        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
884
        }))
885

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

    
891
    elif disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
892
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
893

    
894
    elif disk_template == constants.DT_PLAIN:
895
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
896
        constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
897
        }))
898

    
899
    elif disk_template == constants.DT_BLOCK:
900
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
901

    
902
    elif disk_template == constants.DT_RBD:
903
      result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], {
904
        constants.LDP_POOL: dt_params[constants.RBD_POOL]
905
        }))
906

    
907
    return result
908

    
909

    
910
class InstancePolicy(ConfigObject):
911
  """Config object representing instance policy limits dictionary.
912

913

914
  Note that this object is not actually used in the config, it's just
915
  used as a placeholder for a few functions.
916

917
  """
918
  @classmethod
919
  def CheckParameterSyntax(cls, ipolicy, check_std):
920
    """ Check the instance policy for validity.
921

922
    """
923
    for param in constants.ISPECS_PARAMETERS:
924
      InstancePolicy.CheckISpecSyntax(ipolicy, param, check_std)
925
    if constants.IPOLICY_DTS in ipolicy:
926
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
927
    for key in constants.IPOLICY_PARAMETERS:
928
      if key in ipolicy:
929
        InstancePolicy.CheckParameter(key, ipolicy[key])
930
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
931
    if wrong_keys:
932
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
933
                                      utils.CommaJoin(wrong_keys))
934

    
935
  @classmethod
936
  def CheckISpecSyntax(cls, ipolicy, name, check_std):
937
    """Check the instance policy for validity on a given key.
938

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

942
    @type ipolicy: dict
943
    @param ipolicy: dictionary with min, max, std specs
944
    @type name: string
945
    @param name: what are the limits for
946
    @type check_std: bool
947
    @param check_std: Whether to check std value or just assume compliance
948
    @raise errors.ConfigureError: when specs for given name are not valid
949

950
    """
951
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
952

    
953
    if check_std:
954
      std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
955
      std_msg = std_v
956
    else:
957
      std_v = min_v
958
      std_msg = "-"
959

    
960
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
961
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
962
           (name,
963
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
964
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
965
            std_msg))
966
    if min_v > std_v or std_v > max_v:
967
      raise errors.ConfigurationError(err)
968

    
969
  @classmethod
970
  def CheckDiskTemplates(cls, disk_templates):
971
    """Checks the disk templates for validity.
972

973
    """
974
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
975
    if wrong:
976
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
977
                                      utils.CommaJoin(wrong))
978

    
979
  @classmethod
980
  def CheckParameter(cls, key, value):
981
    """Checks a parameter.
982

983
    Currently we expect all parameters to be float values.
984

985
    """
986
    try:
987
      float(value)
988
    except (TypeError, ValueError), err:
989
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
990
                                      " '%s', error: %s" % (key, value, err))
991

    
992

    
993
class Instance(TaggableObject):
994
  """Config object representing an instance."""
995
  __slots__ = [
996
    "name",
997
    "primary_node",
998
    "os",
999
    "hypervisor",
1000
    "hvparams",
1001
    "beparams",
1002
    "osparams",
1003
    "admin_state",
1004
    "nics",
1005
    "disks",
1006
    "disk_template",
1007
    "network_port",
1008
    "serial_no",
1009
    ] + _TIMESTAMPS + _UUID
1010

    
1011
  def _ComputeSecondaryNodes(self):
1012
    """Compute the list of secondary nodes.
1013

1014
    This is a simple wrapper over _ComputeAllNodes.
1015

1016
    """
1017
    all_nodes = set(self._ComputeAllNodes())
1018
    all_nodes.discard(self.primary_node)
1019
    return tuple(all_nodes)
1020

    
1021
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1022
                             "List of secondary nodes")
1023

    
1024
  def _ComputeAllNodes(self):
1025
    """Compute the list of all nodes.
1026

1027
    Since the data is already there (in the drbd disks), keeping it as
1028
    a separate normal attribute is redundant and if not properly
1029
    synchronised can cause problems. Thus it's better to compute it
1030
    dynamically.
1031

1032
    """
1033
    def _Helper(nodes, device):
1034
      """Recursively computes nodes given a top device."""
1035
      if device.dev_type in constants.LDS_DRBD:
1036
        nodea, nodeb = device.logical_id[:2]
1037
        nodes.add(nodea)
1038
        nodes.add(nodeb)
1039
      if device.children:
1040
        for child in device.children:
1041
          _Helper(nodes, child)
1042

    
1043
    all_nodes = set()
1044
    all_nodes.add(self.primary_node)
1045
    for device in self.disks:
1046
      _Helper(all_nodes, device)
1047
    return tuple(all_nodes)
1048

    
1049
  all_nodes = property(_ComputeAllNodes, None, None,
1050
                       "List of all nodes of the instance")
1051

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

1055
    This function figures out what logical volumes should belong on
1056
    which nodes, recursing through a device tree.
1057

1058
    @param lvmap: optional dictionary to receive the
1059
        'node' : ['lv', ...] data.
1060

1061
    @return: None if lvmap arg is given, otherwise, a dictionary of
1062
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1063
        volumeN is of the form "vg_name/lv_name", compatible with
1064
        GetVolumeList()
1065

1066
    """
1067
    if node is None:
1068
      node = self.primary_node
1069

    
1070
    if lvmap is None:
1071
      lvmap = {
1072
        node: [],
1073
        }
1074
      ret = lvmap
1075
    else:
1076
      if not node in lvmap:
1077
        lvmap[node] = []
1078
      ret = None
1079

    
1080
    if not devs:
1081
      devs = self.disks
1082

    
1083
    for dev in devs:
1084
      if dev.dev_type == constants.LD_LV:
1085
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1086

    
1087
      elif dev.dev_type in constants.LDS_DRBD:
1088
        if dev.children:
1089
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1090
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1091

    
1092
      elif dev.children:
1093
        self.MapLVsByNode(lvmap, dev.children, node)
1094

    
1095
    return ret
1096

    
1097
  def FindDisk(self, idx):
1098
    """Find a disk given having a specified index.
1099

1100
    This is just a wrapper that does validation of the index.
1101

1102
    @type idx: int
1103
    @param idx: the disk index
1104
    @rtype: L{Disk}
1105
    @return: the corresponding disk
1106
    @raise errors.OpPrereqError: when the given index is not valid
1107

1108
    """
1109
    try:
1110
      idx = int(idx)
1111
      return self.disks[idx]
1112
    except (TypeError, ValueError), err:
1113
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1114
                                 errors.ECODE_INVAL)
1115
    except IndexError:
1116
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1117
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1118
                                 errors.ECODE_INVAL)
1119

    
1120
  def ToDict(self):
1121
    """Instance-specific conversion to standard python types.
1122

1123
    This replaces the children lists of objects with lists of standard
1124
    python types.
1125

1126
    """
1127
    bo = super(Instance, self).ToDict()
1128

    
1129
    for attr in "nics", "disks":
1130
      alist = bo.get(attr, None)
1131
      if alist:
1132
        nlist = self._ContainerToDicts(alist)
1133
      else:
1134
        nlist = []
1135
      bo[attr] = nlist
1136
    return bo
1137

    
1138
  @classmethod
1139
  def FromDict(cls, val):
1140
    """Custom function for instances.
1141

1142
    """
1143
    if "admin_state" not in val:
1144
      if val.get("admin_up", False):
1145
        val["admin_state"] = constants.ADMINST_UP
1146
      else:
1147
        val["admin_state"] = constants.ADMINST_DOWN
1148
    if "admin_up" in val:
1149
      del val["admin_up"]
1150
    obj = super(Instance, cls).FromDict(val)
1151
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1152
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1153
    return obj
1154

    
1155
  def UpgradeConfig(self):
1156
    """Fill defaults for missing configuration values.
1157

1158
    """
1159
    for nic in self.nics:
1160
      nic.UpgradeConfig()
1161
    for disk in self.disks:
1162
      disk.UpgradeConfig()
1163
    if self.hvparams:
1164
      for key in constants.HVC_GLOBALS:
1165
        try:
1166
          del self.hvparams[key]
1167
        except KeyError:
1168
          pass
1169
    if self.osparams is None:
1170
      self.osparams = {}
1171
    UpgradeBeParams(self.beparams)
1172

    
1173

    
1174
class OS(ConfigObject):
1175
  """Config object representing an operating system.
1176

1177
  @type supported_parameters: list
1178
  @ivar supported_parameters: a list of tuples, name and description,
1179
      containing the supported parameters by this OS
1180

1181
  @type VARIANT_DELIM: string
1182
  @cvar VARIANT_DELIM: the variant delimiter
1183

1184
  """
1185
  __slots__ = [
1186
    "name",
1187
    "path",
1188
    "api_versions",
1189
    "create_script",
1190
    "export_script",
1191
    "import_script",
1192
    "rename_script",
1193
    "verify_script",
1194
    "supported_variants",
1195
    "supported_parameters",
1196
    ]
1197

    
1198
  VARIANT_DELIM = "+"
1199

    
1200
  @classmethod
1201
  def SplitNameVariant(cls, name):
1202
    """Splits the name into the proper name and variant.
1203

1204
    @param name: the OS (unprocessed) name
1205
    @rtype: list
1206
    @return: a list of two elements; if the original name didn't
1207
        contain a variant, it's returned as an empty string
1208

1209
    """
1210
    nv = name.split(cls.VARIANT_DELIM, 1)
1211
    if len(nv) == 1:
1212
      nv.append("")
1213
    return nv
1214

    
1215
  @classmethod
1216
  def GetName(cls, name):
1217
    """Returns the proper name of the os (without the variant).
1218

1219
    @param name: the OS (unprocessed) name
1220

1221
    """
1222
    return cls.SplitNameVariant(name)[0]
1223

    
1224
  @classmethod
1225
  def GetVariant(cls, name):
1226
    """Returns the variant the os (without the base name).
1227

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

1230
    """
1231
    return cls.SplitNameVariant(name)[1]
1232

    
1233

    
1234
class NodeHvState(ConfigObject):
1235
  """Hypvervisor state on a node.
1236

1237
  @ivar mem_total: Total amount of memory
1238
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1239
    available)
1240
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1241
    rounding
1242
  @ivar mem_inst: Memory used by instances living on node
1243
  @ivar cpu_total: Total node CPU core count
1244
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1245

1246
  """
1247
  __slots__ = [
1248
    "mem_total",
1249
    "mem_node",
1250
    "mem_hv",
1251
    "mem_inst",
1252
    "cpu_total",
1253
    "cpu_node",
1254
    ] + _TIMESTAMPS
1255

    
1256

    
1257
class NodeDiskState(ConfigObject):
1258
  """Disk state on a node.
1259

1260
  """
1261
  __slots__ = [
1262
    "total",
1263
    "reserved",
1264
    "overhead",
1265
    ] + _TIMESTAMPS
1266

    
1267

    
1268
class Node(TaggableObject):
1269
  """Config object representing a node.
1270

1271
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1272
  @ivar hv_state_static: Hypervisor state overriden by user
1273
  @ivar disk_state: Disk state (e.g. free space)
1274
  @ivar disk_state_static: Disk state overriden by user
1275

1276
  """
1277
  __slots__ = [
1278
    "name",
1279
    "primary_ip",
1280
    "secondary_ip",
1281
    "serial_no",
1282
    "master_candidate",
1283
    "offline",
1284
    "drained",
1285
    "group",
1286
    "master_capable",
1287
    "vm_capable",
1288
    "ndparams",
1289
    "powered",
1290
    "hv_state",
1291
    "hv_state_static",
1292
    "disk_state",
1293
    "disk_state_static",
1294
    ] + _TIMESTAMPS + _UUID
1295

    
1296
  def UpgradeConfig(self):
1297
    """Fill defaults for missing configuration values.
1298

1299
    """
1300
    # pylint: disable=E0203
1301
    # because these are "defined" via slots, not manually
1302
    if self.master_capable is None:
1303
      self.master_capable = True
1304

    
1305
    if self.vm_capable is None:
1306
      self.vm_capable = True
1307

    
1308
    if self.ndparams is None:
1309
      self.ndparams = {}
1310

    
1311
    if self.powered is None:
1312
      self.powered = True
1313

    
1314
  def ToDict(self):
1315
    """Custom function for serializing.
1316

1317
    """
1318
    data = super(Node, self).ToDict()
1319

    
1320
    hv_state = data.get("hv_state", None)
1321
    if hv_state is not None:
1322
      data["hv_state"] = self._ContainerToDicts(hv_state)
1323

    
1324
    disk_state = data.get("disk_state", None)
1325
    if disk_state is not None:
1326
      data["disk_state"] = \
1327
        dict((key, self._ContainerToDicts(value))
1328
             for (key, value) in disk_state.items())
1329

    
1330
    return data
1331

    
1332
  @classmethod
1333
  def FromDict(cls, val):
1334
    """Custom function for deserializing.
1335

1336
    """
1337
    obj = super(Node, cls).FromDict(val)
1338

    
1339
    if obj.hv_state is not None:
1340
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1341

    
1342
    if obj.disk_state is not None:
1343
      obj.disk_state = \
1344
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1345
             for (key, value) in obj.disk_state.items())
1346

    
1347
    return obj
1348

    
1349

    
1350
class NodeGroup(TaggableObject):
1351
  """Config object representing a node group."""
1352
  __slots__ = [
1353
    "name",
1354
    "members",
1355
    "ndparams",
1356
    "diskparams",
1357
    "ipolicy",
1358
    "serial_no",
1359
    "hv_state_static",
1360
    "disk_state_static",
1361
    "alloc_policy",
1362
    ] + _TIMESTAMPS + _UUID
1363

    
1364
  def ToDict(self):
1365
    """Custom function for nodegroup.
1366

1367
    This discards the members object, which gets recalculated and is only kept
1368
    in memory.
1369

1370
    """
1371
    mydict = super(NodeGroup, self).ToDict()
1372
    del mydict["members"]
1373
    return mydict
1374

    
1375
  @classmethod
1376
  def FromDict(cls, val):
1377
    """Custom function for nodegroup.
1378

1379
    The members slot is initialized to an empty list, upon deserialization.
1380

1381
    """
1382
    obj = super(NodeGroup, cls).FromDict(val)
1383
    obj.members = []
1384
    return obj
1385

    
1386
  def UpgradeConfig(self):
1387
    """Fill defaults for missing configuration values.
1388

1389
    """
1390
    if self.ndparams is None:
1391
      self.ndparams = {}
1392

    
1393
    if self.serial_no is None:
1394
      self.serial_no = 1
1395

    
1396
    if self.alloc_policy is None:
1397
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1398

    
1399
    # We only update mtime, and not ctime, since we would not be able
1400
    # to provide a correct value for creation time.
1401
    if self.mtime is None:
1402
      self.mtime = time.time()
1403

    
1404
    if self.diskparams is None:
1405
      self.diskparams = {}
1406
    if self.ipolicy is None:
1407
      self.ipolicy = MakeEmptyIPolicy()
1408

    
1409
  def FillND(self, node):
1410
    """Return filled out ndparams for L{objects.Node}
1411

1412
    @type node: L{objects.Node}
1413
    @param node: A Node object to fill
1414
    @return a copy of the node's ndparams with defaults filled
1415

1416
    """
1417
    return self.SimpleFillND(node.ndparams)
1418

    
1419
  def SimpleFillND(self, ndparams):
1420
    """Fill a given ndparams dict with defaults.
1421

1422
    @type ndparams: dict
1423
    @param ndparams: the dict to fill
1424
    @rtype: dict
1425
    @return: a copy of the passed in ndparams with missing keys filled
1426
        from the node group defaults
1427

1428
    """
1429
    return FillDict(self.ndparams, ndparams)
1430

    
1431

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

    
1476
  def UpgradeConfig(self):
1477
    """Fill defaults for missing configuration values.
1478

1479
    """
1480
    # pylint: disable=E0203
1481
    # because these are "defined" via slots, not manually
1482
    if self.hvparams is None:
1483
      self.hvparams = constants.HVC_DEFAULTS
1484
    else:
1485
      for hypervisor in self.hvparams:
1486
        self.hvparams[hypervisor] = FillDict(
1487
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1488

    
1489
    if self.os_hvp is None:
1490
      self.os_hvp = {}
1491

    
1492
    # osparams added before 2.2
1493
    if self.osparams is None:
1494
      self.osparams = {}
1495

    
1496
    self.ndparams = UpgradeNDParams(self.ndparams)
1497

    
1498
    self.beparams = UpgradeGroupedParams(self.beparams,
1499
                                         constants.BEC_DEFAULTS)
1500
    for beparams_group in self.beparams:
1501
      UpgradeBeParams(self.beparams[beparams_group])
1502

    
1503
    migrate_default_bridge = not self.nicparams
1504
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1505
                                          constants.NICC_DEFAULTS)
1506
    if migrate_default_bridge:
1507
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1508
        self.default_bridge
1509

    
1510
    if self.modify_etc_hosts is None:
1511
      self.modify_etc_hosts = True
1512

    
1513
    if self.modify_ssh_setup is None:
1514
      self.modify_ssh_setup = True
1515

    
1516
    # default_bridge is no longer used in 2.1. The slot is left there to
1517
    # support auto-upgrading. It can be removed once we decide to deprecate
1518
    # upgrading straight from 2.0.
1519
    if self.default_bridge is not None:
1520
      self.default_bridge = None
1521

    
1522
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1523
    # code can be removed once upgrading straight from 2.0 is deprecated.
1524
    if self.default_hypervisor is not None:
1525
      self.enabled_hypervisors = ([self.default_hypervisor] +
1526
                                  [hvname for hvname in self.enabled_hypervisors
1527
                                   if hvname != self.default_hypervisor])
1528
      self.default_hypervisor = None
1529

    
1530
    # maintain_node_health added after 2.1.1
1531
    if self.maintain_node_health is None:
1532
      self.maintain_node_health = False
1533

    
1534
    if self.uid_pool is None:
1535
      self.uid_pool = []
1536

    
1537
    if self.default_iallocator is None:
1538
      self.default_iallocator = ""
1539

    
1540
    # reserved_lvs added before 2.2
1541
    if self.reserved_lvs is None:
1542
      self.reserved_lvs = []
1543

    
1544
    # hidden and blacklisted operating systems added before 2.2.1
1545
    if self.hidden_os is None:
1546
      self.hidden_os = []
1547

    
1548
    if self.blacklisted_os is None:
1549
      self.blacklisted_os = []
1550

    
1551
    # primary_ip_family added before 2.3
1552
    if self.primary_ip_family is None:
1553
      self.primary_ip_family = AF_INET
1554

    
1555
    if self.master_netmask is None:
1556
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1557
      self.master_netmask = ipcls.iplen
1558

    
1559
    if self.prealloc_wipe_disks is None:
1560
      self.prealloc_wipe_disks = False
1561

    
1562
    # shared_file_storage_dir added before 2.5
1563
    if self.shared_file_storage_dir is None:
1564
      self.shared_file_storage_dir = ""
1565

    
1566
    if self.use_external_mip_script is None:
1567
      self.use_external_mip_script = False
1568

    
1569
    if self.diskparams:
1570
      self.diskparams = UpgradeDiskParams(self.diskparams)
1571
    else:
1572
      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1573

    
1574
    # instance policy added before 2.6
1575
    if self.ipolicy is None:
1576
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1577
    else:
1578
      # we can either make sure to upgrade the ipolicy always, or only
1579
      # do it in some corner cases (e.g. missing keys); note that this
1580
      # will break any removal of keys from the ipolicy dict
1581
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1582

    
1583
  @property
1584
  def primary_hypervisor(self):
1585
    """The first hypervisor is the primary.
1586

1587
    Useful, for example, for L{Node}'s hv/disk state.
1588

1589
    """
1590
    return self.enabled_hypervisors[0]
1591

    
1592
  def ToDict(self):
1593
    """Custom function for cluster.
1594

1595
    """
1596
    mydict = super(Cluster, self).ToDict()
1597
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1598
    return mydict
1599

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

1604
    """
1605
    obj = super(Cluster, cls).FromDict(val)
1606
    if not isinstance(obj.tcpudp_port_pool, set):
1607
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1608
    return obj
1609

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

1613
    @param diskparams: The diskparams
1614
    @return: The defaults dict
1615

1616
    """
1617
    return FillDiskParams(self.diskparams, diskparams)
1618

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

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

1627
    """
1628
    if skip_keys is None:
1629
      skip_keys = []
1630

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

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

    
1640
    return ret_dict
1641

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

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

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

    
1662
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1663
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1664

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

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

1677
    """
1678
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1679
                             instance.hvparams, skip_globals)
1680

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

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

1690
    """
1691
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1692

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

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

1702
    """
1703
    return self.SimpleFillBE(instance.beparams)
1704

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

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

1714
    """
1715
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1716

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

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

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

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

1741
    """
1742
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1743

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

1748
    """
1749
    return FillDict(constants.DS_DEFAULTS, disk_state)
1750

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

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

1760
    """
1761
    return self.SimpleFillND(nodegroup.FillND(node))
1762

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

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

1772
    """
1773
    return FillDict(self.ndparams, ndparams)
1774

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

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

1784
    """
1785
    return FillIPolicy(self.ipolicy, ipolicy)
1786

    
1787

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

    
1800

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

    
1815

    
1816
class ImportExportOptions(ConfigObject):
1817
  """Options for import/export daemon
1818

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

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

    
1836

    
1837
class ConfdRequest(ConfigObject):
1838
  """Object holding a confd request.
1839

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

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

    
1853

    
1854
class ConfdReply(ConfigObject):
1855
  """Object holding a confd reply.
1856

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

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

    
1870

    
1871
class QueryFieldDefinition(ConfigObject):
1872
  """Object holding a query field definition.
1873

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

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

    
1887

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

    
1893
  def ToDict(self):
1894
    """Custom function for serializing.
1895

1896
    """
1897
    mydict = super(_QueryResponseBase, self).ToDict()
1898
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1899
    return mydict
1900

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

1905
    """
1906
    obj = super(_QueryResponseBase, cls).FromDict(val)
1907
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1908
    return obj
1909

    
1910

    
1911
class QueryResponse(_QueryResponseBase):
1912
  """Object holding the response to a query.
1913

1914
  @ivar fields: List of L{QueryFieldDefinition} objects
1915
  @ivar data: Requested data
1916

1917
  """
1918
  __slots__ = [
1919
    "data",
1920
    ]
1921

    
1922

    
1923
class QueryFieldsRequest(ConfigObject):
1924
  """Object holding a request for querying available fields.
1925

1926
  """
1927
  __slots__ = [
1928
    "what",
1929
    "fields",
1930
    ]
1931

    
1932

    
1933
class QueryFieldsResponse(_QueryResponseBase):
1934
  """Object holding the response to a query for fields.
1935

1936
  @ivar fields: List of L{QueryFieldDefinition} objects
1937

1938
  """
1939
  __slots__ = []
1940

    
1941

    
1942
class MigrationStatus(ConfigObject):
1943
  """Object holding the status of a migration.
1944

1945
  """
1946
  __slots__ = [
1947
    "status",
1948
    "transferred_ram",
1949
    "total_ram",
1950
    ]
1951

    
1952

    
1953
class InstanceConsole(ConfigObject):
1954
  """Object describing how to access the console of an instance.
1955

1956
  """
1957
  __slots__ = [
1958
    "instance",
1959
    "kind",
1960
    "message",
1961
    "host",
1962
    "port",
1963
    "user",
1964
    "command",
1965
    "display",
1966
    ]
1967

    
1968
  def Validate(self):
1969
    """Validates contents of this object.
1970

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

    
1991

    
1992
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1993
  """Simple wrapper over ConfigParse that allows serialization.
1994

1995
  This class is basically ConfigParser.SafeConfigParser with two
1996
  additional methods that allow it to serialize/unserialize to/from a
1997
  buffer.
1998

1999
  """
2000
  def Dumps(self):
2001
    """Dump this instance and return the string representation."""
2002
    buf = StringIO()
2003
    self.write(buf)
2004
    return buf.getvalue()
2005

    
2006
  @classmethod
2007
  def Loads(cls, data):
2008
    """Load data from a string."""
2009
    buf = StringIO(data)
2010
    cfp = cls()
2011
    cfp.readfp(buf)
2012
    return cfp