Revision 32683096

b/Makefile.am
225 225
	lib/mcpu.py \
226 226
	lib/netutils.py \
227 227
	lib/objects.py \
228
	lib/objectutils.py \
228 229
	lib/opcodes.py \
229 230
	lib/ovf.py \
230 231
	lib/qlang.py \
......
877 878
	test/ganeti.mcpu_unittest.py \
878 879
	test/ganeti.netutils_unittest.py \
879 880
	test/ganeti.objects_unittest.py \
881
	test/ganeti.objectutils_unittest.py \
880 882
	test/ganeti.opcodes_unittest.py \
881 883
	test/ganeti.ovf_unittest.py \
882 884
	test/ganeti.qlang_unittest.py \
b/lib/objects.py
44 44
from ganeti import errors
45 45
from ganeti import constants
46 46
from ganeti import netutils
47
from ganeti import objectutils
47 48
from ganeti import utils
48 49

  
49 50
from socket import AF_INET
......
191 192
    ])
192 193

  
193 194

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

  
197 198
  It has the following properties:
......
206 207
  """
207 208
  __slots__ = []
208 209

  
209
  def __init__(self, **kwargs):
210
    for k, v in kwargs.iteritems():
211
      setattr(self, k, v)
212

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

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

  
225
  @classmethod
226
  def _all_slots(cls):
227
    """Compute the list of all declared slots for a class.
222
  def Validate(self):
223
    """Validates the slots.
228 224

  
229 225
    """
230
    slots = []
231
    for parent in cls.__mro__:
232
      slots.extend(getattr(parent, "__slots__", []))
233
    return slots
234

  
235
  #: Public getter for the defined slots
236
  GetAllSlots = _all_slots
237 226

  
238 227
  def ToDict(self):
239 228
    """Convert to a dict holding only standard python types.
......
246 235

  
247 236
    """
248 237
    result = {}
249
    for name in self._all_slots():
238
    for name in self.GetAllSlots():
250 239
      value = getattr(self, name, None)
251 240
      if value is not None:
252 241
        result[name] = value
b/lib/objectutils.py
1
#
2
#
3

  
4
# Copyright (C) 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
"""Module for object related utils."""
22

  
23

  
24
class AutoSlots(type):
25
  """Meta base class for __slots__ definitions.
26

  
27
  """
28
  def __new__(mcs, name, bases, attrs):
29
    """Called when a class should be created.
30

  
31
    @param mcs: The meta class
32
    @param name: Name of created class
33
    @param bases: Base classes
34
    @type attrs: dict
35
    @param attrs: Class attributes
36

  
37
    """
38
    assert "__slots__" not in attrs, \
39
      "Class '%s' defines __slots__ when it should not" % name
40

  
41
    attrs["__slots__"] = mcs._GetSlots(attrs)
42

  
43
    return type.__new__(mcs, name, bases, attrs)
44

  
45
  @classmethod
46
  def _GetSlots(mcs, attrs):
47
    """Used to get the list of defined slots.
48

  
49
    @param attrs: The attributes of the class
50

  
51
    """
52
    raise NotImplementedError
53

  
54

  
55
class ValidatedSlots(object):
56
  """Sets and validates slots.
57

  
58
  """
59
  __slots__ = []
60

  
61
  def __init__(self, **kwargs):
62
    """Constructor for BaseOpCode.
63

  
64
    The constructor takes only keyword arguments and will set
65
    attributes on this object based on the passed arguments. As such,
66
    it means that you should not pass arguments which are not in the
67
    __slots__ attribute for this class.
68

  
69
    """
70
    slots = self.GetAllSlots()
71
    for (key, value) in kwargs.items():
72
      if key not in slots:
73
        raise TypeError("Object %s doesn't support the parameter '%s'" %
74
                        (self.__class__.__name__, key))
75
      setattr(self, key, value)
76

  
77
  @classmethod
78
  def GetAllSlots(cls):
79
    """Compute the list of all declared slots for a class.
80

  
81
    """
82
    slots = []
83
    for parent in cls.__mro__:
84
      slots.extend(getattr(parent, "__slots__", []))
85
    return slots
86

  
87
  def Validate(self):
88
    """Validates the slots.
89

  
90
    This method must be implemented by the child classes.
91

  
92
    """
93
    raise NotImplementedError
b/lib/opcodes.py
40 40
from ganeti import errors
41 41
from ganeti import ht
42 42
from ganeti import objects
43
from ganeti import objectutils
43 44

  
44 45

  
45 46
# Common opcode attributes
......
342 343
                 "Storage type")
343 344

  
344 345

  
345
class _AutoOpParamSlots(type):
346
class _AutoOpParamSlots(objectutils.AutoSlots):
346 347
  """Meta class for opcode definitions.
347 348

  
348 349
  """
......
356 357
    @param attrs: Class attributes
357 358

  
358 359
    """
359
    assert "__slots__" not in attrs, \
360
      "Class '%s' defines __slots__ when it should use OP_PARAMS" % name
361 360
    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
362 361

  
362
    slots = mcs._GetSlots(attrs)
363
    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
364
      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
365

  
363 366
    attrs["OP_ID"] = _NameToId(name)
364 367

  
368
    return objectutils.AutoSlots.__new__(mcs, name, bases, attrs)
369

  
370
  @classmethod
371
  def _GetSlots(mcs, attrs):
372
    """Build the slots out of OP_PARAMS.
373

  
374
    """
365 375
    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
366 376
    params = attrs.setdefault("OP_PARAMS", [])
367 377

  
368 378
    # Use parameter names as slots
369
    slots = [pname for (pname, _, _, _) in params]
370

  
371
    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
372
      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
373

  
374
    attrs["__slots__"] = slots
375

  
376
    return type.__new__(mcs, name, bases, attrs)
379
    return [pname for (pname, _, _, _) in params]
377 380

  
378 381

  
379
class BaseOpCode(object):
382
class BaseOpCode(objectutils.ValidatedSlots):
380 383
  """A simple serializable object.
381 384

  
382 385
  This object serves as a parent class for OpCode without any custom
......
387 390
  # as OP_ID is dynamically defined
388 391
  __metaclass__ = _AutoOpParamSlots
389 392

  
390
  def __init__(self, **kwargs):
391
    """Constructor for BaseOpCode.
392

  
393
    The constructor takes only keyword arguments and will set
394
    attributes on this object based on the passed arguments. As such,
395
    it means that you should not pass arguments which are not in the
396
    __slots__ attribute for this class.
397

  
398
    """
399
    slots = self._all_slots()
400
    for key in kwargs:
401
      if key not in slots:
402
        raise TypeError("Object %s doesn't support the parameter '%s'" %
403
                        (self.__class__.__name__, key))
404
      setattr(self, key, kwargs[key])
405

  
406 393
  def __getstate__(self):
407 394
    """Generic serializer.
408 395

  
......
414 401

  
415 402
    """
416 403
    state = {}
417
    for name in self._all_slots():
404
    for name in self.GetAllSlots():
418 405
      if hasattr(self, name):
419 406
        state[name] = getattr(self, name)
420 407
    return state
......
433 420
      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
434 421
                       type(state))
435 422

  
436
    for name in self._all_slots():
423
    for name in self.GetAllSlots():
437 424
      if name not in state and hasattr(self, name):
438 425
        delattr(self, name)
439 426

  
......
441 428
      setattr(self, name, state[name])
442 429

  
443 430
  @classmethod
444
  def _all_slots(cls):
445
    """Compute the list of all declared slots for a class.
446

  
447
    """
448
    slots = []
449
    for parent in cls.__mro__:
450
      slots.extend(getattr(parent, "__slots__", []))
451
    return slots
452

  
453
  @classmethod
454 431
  def GetAllParams(cls):
455 432
    """Compute list of all parameters for an opcode.
456 433

  
......
460 437
      slots.extend(getattr(parent, "OP_PARAMS", []))
461 438
    return slots
462 439

  
463
  def Validate(self, set_defaults):
440
  def Validate(self, set_defaults): # pylint: disable=W0221
464 441
    """Validate opcode parameters, optionally setting default values.
465 442

  
466 443
    @type set_defaults: bool
b/test/ganeti.objectutils_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 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
"""Script for unittesting the objectutils module"""
23

  
24

  
25
import unittest
26

  
27
from ganeti import objectutils
28

  
29
import testutils
30

  
31

  
32
class SlotsAutoSlot(objectutils.AutoSlots):
33
  @classmethod
34
  def _GetSlots(mcs, attr):
35
    return attr["SLOTS"]
36

  
37

  
38
class AutoSlotted(object):
39
  __metaclass__ = SlotsAutoSlot
40

  
41
  SLOTS = ["foo", "bar", "baz"]
42

  
43

  
44
class TestAutoSlot(unittest.TestCase):
45
  def test(self):
46
    slotted = AutoSlotted()
47
    self.assertEqual(slotted.__slots__, AutoSlotted.SLOTS)
48

  
49
if __name__ == "__main__":
50
  testutils.GanetiTestProgram()
b/test/ganeti.opcodes_unittest.py
77 77
        {"dry_run": False, "debug_level": 0, },
78 78

  
79 79
        # All variables
80
        dict([(name, False) for name in cls._all_slots()])
80
        dict([(name, False) for name in cls.GetAllSlots()])
81 81
        ]
82 82

  
83 83
      for i in args:
......
95 95
        self._checkSummary(restored)
96 96

  
97 97
        for name in ["x_y_z", "hello_world"]:
98
          assert name not in cls._all_slots()
98
          assert name not in cls.GetAllSlots()
99 99
          for value in [None, True, False, [], "Hello World"]:
100 100
            self.assertRaises(AttributeError, setattr, op, name, value)
101 101

  
......
158 158
    self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
159 159

  
160 160
    for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
161
      all_slots = cls._all_slots()
161
      all_slots = cls.GetAllSlots()
162 162

  
163 163
      self.assertEqual(len(set(all_slots) & supported_by_all), 3,
164 164
                       msg=("Opcode %s doesn't support all base"
b/tools/cfgshell
95 95
    if isinstance(obj, objects.ConfigObject):
96 96
      # pylint: disable=W0212
97 97
      # yes, we're using a protected member
98
      for name in obj._all_slots():
98
      for name in obj.GetAllSlots():
99 99
        child = getattr(obj, name, None)
100 100
        if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
101 101
          dirs.append(name)

Also available in: Unified diff