Implement utils.RunParts and use it for hooks
[ganeti-local] / lib / objects.py
index e7eb537..7fb7d5b 100644 (file)
@@ -26,11 +26,12 @@ pass to and from external parties.
 
 """
 
-# pylint: disable-msg=E0203
+# pylint: disable-msg=E0203,W0201
 
 # E0203: Access to member %r before its definition, since we use
 # objects.py which doesn't explicitely initialise its members
 
+# W0201: Attribute '%s' defined outside __init__
 
 import ConfigParser
 import re
@@ -47,7 +48,7 @@ __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
 _TIMESTAMPS = ["ctime", "mtime"]
 _UUID = ["uuid"]
 
-def FillDict(defaults_dict, custom_dict, skip_keys=[]):
+def FillDict(defaults_dict, custom_dict, skip_keys=None):
   """Basic function to apply settings on top a default dict.
 
   @type defaults_dict: dict
@@ -62,11 +63,12 @@ def FillDict(defaults_dict, custom_dict, skip_keys=[]):
   """
   ret_dict = copy.deepcopy(defaults_dict)
   ret_dict.update(custom_dict)
-  for k in skip_keys:
-    try:
-      del ret_dict[k]
-    except KeyError:
-      pass
+  if skip_keys:
+    for k in skip_keys:
+      try:
+        del ret_dict[k]
+      except KeyError:
+        pass
   return ret_dict
 
 
@@ -107,16 +109,27 @@ class ConfigObject(object):
       setattr(self, k, v)
 
   def __getattr__(self, name):
-    if name not in self.__slots__:
+    if name not in self._all_slots():
       raise AttributeError("Invalid object attribute %s.%s" %
                            (type(self).__name__, name))
     return None
 
   def __setstate__(self, state):
+    slots = self._all_slots()
     for name in state:
-      if name in self.__slots__:
+      if name in slots:
         setattr(self, name, state[name])
 
+  @classmethod
+  def _all_slots(cls):
+    """Compute the list of all declared slots for a class.
+
+    """
+    slots = []
+    for parent in cls.__mro__:
+      slots.extend(getattr(parent, "__slots__", []))
+    return slots
+
   def ToDict(self):
     """Convert to a dict holding only standard python types.
 
@@ -128,7 +141,7 @@ class ConfigObject(object):
 
     """
     result = {}
-    for name in self.__slots__:
+    for name in self._all_slots():
       value = getattr(self, name, None)
       if value is not None:
         result[name] = value
@@ -153,7 +166,7 @@ class ConfigObject(object):
       raise errors.ConfigurationError("Invalid object passed to FromDict:"
                                       " expected dict, got %s" % type(val))
     val_str = dict([(str(k), v) for k, v in val.iteritems()])
-    obj = cls(**val_str)
+    obj = cls(**val_str) # pylint: disable-msg=W0142
     return obj
 
   @staticmethod
@@ -221,7 +234,7 @@ class TaggableObject(ConfigObject):
   """An generic class supporting tags.
 
   """
-  __slots__ = ConfigObject.__slots__ + ["tags"]
+  __slots__ = ["tags"]
   VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
 
   @classmethod
@@ -633,7 +646,7 @@ class Disk(ConfigObject):
 
 class Instance(TaggableObject):
   """Config object representing an instance."""
-  __slots__ = TaggableObject.__slots__ + [
+  __slots__ = [
     "name",
     "primary_node",
     "os",
@@ -745,7 +758,7 @@ class Instance(TaggableObject):
     try:
       idx = int(idx)
       return self.disks[idx]
-    except ValueError, err:
+    except (TypeError, ValueError), err:
       raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
                                  errors.ECODE_INVAL)
     except IndexError:
@@ -813,7 +826,7 @@ class OS(ConfigObject):
 
 class Node(TaggableObject):
   """Config object representing a node."""
-  __slots__ = TaggableObject.__slots__ + [
+  __slots__ = [
     "name",
     "primary_ip",
     "secondary_ip",
@@ -826,7 +839,7 @@ class Node(TaggableObject):
 
 class Cluster(TaggableObject):
   """Config object representing the cluster."""
-  __slots__ = TaggableObject.__slots__ + [
+  __slots__ = [
     "serial_no",
     "rsahostkeypub",
     "highest_used_port",