Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 47c28c5b

History | View | Annotate | Download (10.8 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 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

    
30
import cPickle
31
from cStringIO import StringIO
32
import ConfigParser
33

    
34
from ganeti import errors
35

    
36

    
37
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
38
           "OS", "Node", "Cluster"]
39

    
40

    
41
class ConfigObject(object):
42
  """A generic config object.
43

44
  It has the following properties:
45

46
    - provides somewhat safe recursive unpickling and pickling for its classes
47
    - unset attributes which are defined in slots are always returned
48
      as None instead of raising an error
49

50
  Classes derived from this must always declare __slots__ (we use many
51
  config objects and the memory reduction is useful.
52

53
  """
54
  __slots__ = []
55

    
56
  def __init__(self, **kwargs):
57
    for i in kwargs:
58
      setattr(self, i, kwargs[i])
59

    
60
  def __getattr__(self, name):
61
    if name not in self.__slots__:
62
      raise AttributeError, ("Invalid object attribute %s.%s" %
63
                             (type(self).__name__, name))
64
    return None
65

    
66
  def __setitem__(self, key, value):
67
    if key not in self.__slots__:
68
      raise KeyError, key
69
    setattr(self, key, value)
70

    
71
  def __getstate__(self):
72
    state = {}
73
    for name in self.__slots__:
74
      if hasattr(self, name):
75
        state[name] = getattr(self, name)
76
    return state
77

    
78
  def __setstate__(self, state):
79
    for name in state:
80
      if name in self.__slots__:
81
        setattr(self, name, state[name])
82

    
83
  @staticmethod
84
  def FindGlobal(module, name):
85
    """Function filtering the allowed classes to be un-pickled.
86

87
    Currently, we only allow the classes from this module which are
88
    derived from ConfigObject.
89

90
    """
91
    # Also support the old module name (ganeti.config)
92
    cls = None
93
    if module == "ganeti.config" or module == "ganeti.objects":
94
      if name == "ConfigData":
95
        cls = ConfigData
96
      elif name == "NIC":
97
        cls = NIC
98
      elif name == "Disk" or name == "BlockDev":
99
        cls = Disk
100
      elif name == "Instance":
101
        cls = Instance
102
      elif name == "OS":
103
        cls = OS
104
      elif name == "Node":
105
        cls = Node
106
      elif name == "Cluster":
107
        cls = Cluster
108
    elif module == "__builtin__":
109
      if name == "set":
110
        cls = set
111
    if cls is None:
112
      raise cPickle.UnpicklingError, ("Class %s.%s not allowed due to"
113
                                      " security concerns" % (module, name))
114
    return cls
115

    
116
  def Dump(self, fobj):
117
    """Dump this instance to a file object.
118

119
    Note that we use the HIGHEST_PROTOCOL, as it brings benefits for
120
    the new classes.
121

122
    """
123
    dumper = cPickle.Pickler(fobj, cPickle.HIGHEST_PROTOCOL)
124
    dumper.dump(self)
125

    
126
  @staticmethod
127
  def Load(fobj):
128
    """Unpickle data from the given stream.
129

130
    This uses the `FindGlobal` function to filter the allowed classes.
131

132
    """
133
    loader = cPickle.Unpickler(fobj)
134
    loader.find_global = ConfigObject.FindGlobal
135
    return loader.load()
136

    
137
  def Dumps(self):
138
    """Dump this instance and return the string representation."""
139
    buf = StringIO()
140
    self.Dump(buf)
141
    return buf.getvalue()
142

    
143
  @staticmethod
144
  def Loads(data):
145
    """Load data from a string."""
146
    return ConfigObject.Load(StringIO(data))
147

    
148

    
149
class ConfigData(ConfigObject):
150
  """Top-level config object."""
151
  __slots__ = ["cluster", "nodes", "instances"]
152

    
153

    
154
class NIC(ConfigObject):
155
  """Config object representing a network card."""
156
  __slots__ = ["mac", "ip", "bridge"]
157

    
158

    
159
class Disk(ConfigObject):
160
  """Config object representing a block device."""
161
  __slots__ = ["dev_type", "logical_id", "physical_id",
162
               "children", "iv_name", "size"]
163

    
164
  def CreateOnSecondary(self):
165
    """Test if this device needs to be created on a secondary node."""
166
    return self.dev_type in ("drbd", "lvm")
167

    
168
  def AssembleOnSecondary(self):
169
    """Test if this device needs to be assembled on a secondary node."""
170
    return self.dev_type in ("drbd", "lvm")
171

    
172
  def OpenOnSecondary(self):
173
    """Test if this device needs to be opened on a secondary node."""
174
    return self.dev_type in ("lvm",)
175

    
176
  def GetNodes(self, node):
177
    """This function returns the nodes this device lives on.
178

179
    Given the node on which the parent of the device lives on (or, in
180
    case of a top-level device, the primary node of the devices'
181
    instance), this function will return a list of nodes on which this
182
    devices needs to (or can) be assembled.
183

184
    """
185
    if self.dev_type == "lvm" or self.dev_type == "md_raid1":
186
      result = [node]
187
    elif self.dev_type == "drbd":
188
      result = [self.logical_id[0], self.logical_id[1]]
189
      if node not in result:
190
        raise errors.ConfigurationError, ("DRBD device passed unknown node")
191
    else:
192
      raise errors.ProgrammerError, "Unhandled device type %s" % self.dev_type
193
    return result
194

    
195
  def ComputeNodeTree(self, parent_node):
196
    """Compute the node/disk tree for this disk and its children.
197

198
    This method, given the node on which the parent disk lives, will
199
    return the list of all (node, disk) pairs which describe the disk
200
    tree in the most compact way. For example, a md/drbd/lvm stack
201
    will be returned as (primary_node, md) and (secondary_node, drbd)
202
    which represents all the top-level devices on the nodes. This
203
    means that on the primary node we need to activate the the md (and
204
    recursively all its children) and on the secondary node we need to
205
    activate the drbd device (and its children, the two lvm volumes).
206

207
    """
208
    my_nodes = self.GetNodes(parent_node)
209
    result = [(node, self) for node in my_nodes]
210
    if not self.children:
211
      # leaf device
212
      return result
213
    for node in my_nodes:
214
      for child in self.children:
215
        child_result = child.ComputeNodeTree(node)
216
        if len(child_result) == 1:
217
          # child (and all its descendants) is simple, doesn't split
218
          # over multiple hosts, so we don't need to describe it, our
219
          # own entry for this node describes it completely
220
          continue
221
        else:
222
          # check if child nodes differ from my nodes; note that
223
          # subdisk can differ from the child itself, and be instead
224
          # one of its descendants
225
          for subnode, subdisk in child_result:
226
            if subnode not in my_nodes:
227
              result.append((subnode, subdisk))
228
            # otherwise child is under our own node, so we ignore this
229
            # entry (but probably the other results in the list will
230
            # be different)
231
    return result
232

    
233

    
234
class Instance(ConfigObject):
235
  """Config object representing an instance."""
236
  __slots__ = [
237
    "name",
238
    "primary_node",
239
    "os",
240
    "status",
241
    "memory",
242
    "vcpus",
243
    "nics",
244
    "disks",
245
    "disk_template",
246
    ]
247

    
248
  def _ComputeSecondaryNodes(self):
249
    """Compute the list of secondary nodes.
250

251
    Since the data is already there (in the drbd disks), keeping it as
252
    a separate normal attribute is redundant and if not properly
253
    synchronised can cause problems. Thus it's better to compute it
254
    dynamically.
255

256
    """
257
    def _Helper(primary, sec_nodes, device):
258
      """Recursively computes secondary nodes given a top device."""
259
      if device.dev_type == 'drbd':
260
        nodea, nodeb, dummy = device.logical_id
261
        if nodea == primary:
262
          candidate = nodeb
263
        else:
264
          candidate = nodea
265
        if candidate not in sec_nodes:
266
          sec_nodes.append(candidate)
267
      if device.children:
268
        for child in device.children:
269
          _Helper(primary, sec_nodes, child)
270

    
271
    secondary_nodes = []
272
    for device in self.disks:
273
      _Helper(self.primary_node, secondary_nodes, device)
274
    return tuple(secondary_nodes)
275

    
276
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
277
                             "List of secondary nodes")
278

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

282
    This function figures out what logical volumes should belong on which
283
    nodes, recursing through a device tree.
284

285
    Args:
286
      lvmap: (optional) a dictionary to receive the 'node' : ['lv', ...] data.
287

288
    Returns:
289
      None if lvmap arg is given.
290
      Otherwise, { 'nodename' : ['volume1', 'volume2', ...], ... }
291

292
    """
293
    if node == None:
294
      node = self.primary_node
295

    
296
    if lvmap is None:
297
      lvmap = { node : [] }
298
      ret = lvmap
299
    else:
300
      if not node in lvmap:
301
        lvmap[node] = []
302
      ret = None
303

    
304
    if not devs:
305
      devs = self.disks
306

    
307
    for dev in devs:
308
      if dev.dev_type == "lvm":
309
        lvmap[node].append(dev.logical_id[1])
310

    
311
      elif dev.dev_type == "drbd":
312
        if dev.logical_id[0] not in lvmap:
313
          lvmap[dev.logical_id[0]] = []
314

    
315
        if dev.logical_id[1] not in lvmap:
316
          lvmap[dev.logical_id[1]] = []
317

    
318
        if dev.children:
319
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
320
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
321

    
322
      elif dev.children:
323
        self.MapLVsByNode(lvmap, dev.children, node)
324

    
325
    return ret
326

    
327

    
328
class OS(ConfigObject):
329
  """Config object representing an operating system."""
330
  __slots__ = [
331
    "name",
332
    "path",
333
    "api_version",
334
    "create_script",
335
    "export_script",
336
    "import_script"
337
    ]
338

    
339

    
340
class Node(ConfigObject):
341
  """Config object representing a node."""
342
  __slots__ = ["name", "primary_ip", "secondary_ip"]
343

    
344

    
345
class Cluster(ConfigObject):
346
  """Config object representing the cluster."""
347
  __slots__ = [
348
    "config_version",
349
    "serial_no",
350
    "rsahostkeypub",
351
    "highest_used_port",
352
    "tcpudp_port_pool",
353
    "mac_prefix",
354
    "volume_group_name",
355
    "default_bridge",
356
    ]
357

    
358
class SerializableConfigParser(ConfigParser.SafeConfigParser):
359
  """Simple wrapper over ConfigParse that allows serialization.
360

361
  This class is basically ConfigParser.SafeConfigParser with two
362
  additional methods that allow it to serialize/unserialize to/from a
363
  buffer.
364

365
  """
366
  def Dumps(self):
367
    """Dump this instance and return the string representation."""
368
    buf = StringIO()
369
    self.write(buf)
370
    return buf.getvalue()
371

    
372
  @staticmethod
373
  def Loads(data):
374
    """Load data from a string."""
375
    buf = StringIO(data)
376
    cfp = SerializableConfigParser()
377
    cfp.readfp(buf)
378
    return cfp