Fix a bug in command line option parsing code
[ganeti-local] / lib / objects.py
index 6e7c33f..69862b6 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@ pass to and from external parties.
 
 """
 
-# pylint: disable-msg=E0203,W0201
+# pylint: disable=E0203,W0201
 
 # E0203: Access to member %r before its definition, since we use
 # objects.py which doesn't explicitely initialise its members
@@ -36,6 +36,7 @@ pass to and from external parties.
 import ConfigParser
 import re
 import copy
+import time
 from cStringIO import StringIO
 
 from ganeti import errors
@@ -169,7 +170,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) # pylint: disable-msg=W0142
+    obj = cls(**val_str) # pylint: disable=W0142
     return obj
 
   @staticmethod
@@ -389,7 +390,7 @@ class ConfigData(ConfigObject):
 
 class NIC(ConfigObject):
   """Config object representing a network card."""
-  __slots__ = ["mac", "ip", "bridge", "nicparams"]
+  __slots__ = ["mac", "ip", "nicparams"]
 
   @classmethod
   def CheckParameterSyntax(cls, nicparams):
@@ -409,21 +410,6 @@ class NIC(ConfigObject):
       err = "Missing bridged nic link"
       raise errors.ConfigurationError(err)
 
-  def UpgradeConfig(self):
-    """Fill defaults for missing configuration values.
-
-    """
-    if self.nicparams is None:
-      self.nicparams = {}
-      if self.bridge is not None:
-        self.nicparams[constants.NIC_MODE] = constants.NIC_MODE_BRIDGED
-        self.nicparams[constants.NIC_LINK] = self.bridge
-    # bridge is no longer used it 2.1. The slot is left there to support
-    # upgrading, but can be removed once upgrades to the current version
-    # straight from 2.0 are deprecated.
-    if self.bridge is not None:
-      self.bridge = None
-
 
 class Disk(ConfigObject):
   """Config object representing a block device."""
@@ -455,6 +441,8 @@ class Disk(ConfigObject):
     """
     if self.dev_type == constants.LD_LV:
       return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
+    elif self.dev_type == constants.LD_BLOCKDEV:
+      return self.logical_id[1]
     return None
 
   def ChildrenNeeded(self):
@@ -497,7 +485,8 @@ class Disk(ConfigObject):
     devices needs to (or can) be assembled.
 
     """
-    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
+    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
+                         constants.LD_BLOCKDEV]:
       result = [node]
     elif self.dev_type in constants.LDS_DRBD:
       result = [self.logical_id[0], self.logical_id[1]]
@@ -542,6 +531,28 @@ class Disk(ConfigObject):
             # be different)
     return result
 
+  def ComputeGrowth(self, amount):
+    """Compute the per-VG growth requirements.
+
+    This only works for VG-based disks.
+
+    @type amount: integer
+    @param amount: the desired increase in (user-visible) disk space
+    @rtype: dict
+    @return: a dictionary of volume-groups and the required size
+
+    """
+    if self.dev_type == constants.LD_LV:
+      return {self.logical_id[0]: amount}
+    elif self.dev_type == constants.LD_DRBD8:
+      if self.children:
+        return self.children[0].ComputeGrowth(amount)
+      else:
+        return {}
+    else:
+      # Other disk types do not require VG space
+      return {}
+
   def RecordGrow(self, amount):
     """Update the size of this disk after growth.
 
@@ -550,7 +561,7 @@ class Disk(ConfigObject):
     actual algorithms from bdev.
 
     """
-    if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
+    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
       self.size += amount
     elif self.dev_type == constants.LD_DRBD8:
       if self.children:
@@ -651,7 +662,7 @@ class Disk(ConfigObject):
 
     """
     if self.dev_type == constants.LD_LV:
-      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
+      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
     elif self.dev_type in constants.LDS_DRBD:
       node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
       val = "<DRBD8("
@@ -778,7 +789,9 @@ class Instance(TaggableObject):
       node = self.primary_node
 
     if lvmap is None:
-      lvmap = { node : [] }
+      lvmap = {
+        node: [],
+        }
       ret = lvmap
     else:
       if not node in lvmap:
@@ -790,7 +803,7 @@ class Instance(TaggableObject):
 
     for dev in devs:
       if dev.dev_type == constants.LD_LV:
-        lvmap[node].append(dev.logical_id[0]+"/"+dev.logical_id[1])
+        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
 
       elif dev.dev_type in constants.LDS_DRBD:
         if dev.children:
@@ -822,7 +835,7 @@ class Instance(TaggableObject):
                                  errors.ECODE_INVAL)
     except IndexError:
       raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
-                                 " 0 to %d" % (idx, len(self.disks)),
+                                 " 0 to %d" % (idx, len(self.disks) - 1),
                                  errors.ECODE_INVAL)
 
   def ToDict(self):
@@ -945,13 +958,14 @@ class Node(TaggableObject):
     "master_capable",
     "vm_capable",
     "ndparams",
+    "powered",
     ] + _TIMESTAMPS + _UUID
 
   def UpgradeConfig(self):
     """Fill defaults for missing configuration values.
 
     """
-    # pylint: disable-msg=E0203
+    # pylint: disable=E0203
     # because these are "defined" via slots, not manually
     if self.master_capable is None:
       self.master_capable = True
@@ -962,13 +976,18 @@ class Node(TaggableObject):
     if self.ndparams is None:
       self.ndparams = {}
 
+    if self.powered is None:
+      self.powered = True
+
 
-class NodeGroup(ConfigObject):
+class NodeGroup(TaggableObject):
   """Config object representing a node group."""
   __slots__ = [
     "name",
     "members",
     "ndparams",
+    "serial_no",
+    "alloc_policy",
     ] + _TIMESTAMPS + _UUID
 
   def ToDict(self):
@@ -1000,8 +1019,19 @@ class NodeGroup(ConfigObject):
     if self.ndparams is None:
       self.ndparams = {}
 
+    if self.serial_no is None:
+      self.serial_no = 1
+
+    if self.alloc_policy is None:
+      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
+
+    # We only update mtime, and not ctime, since we would not be able to provide
+    # a correct value for creation time.
+    if self.mtime is None:
+      self.mtime = time.time()
+
   def FillND(self, node):
-    """Return filled out ndparams for L{object.Node}
+    """Return filled out ndparams for L{objects.Node}
 
     @type node: L{objects.Node}
     @param node: A Node object to fill
@@ -1017,7 +1047,7 @@ class NodeGroup(ConfigObject):
     @param ndparams: the dict to fill
     @rtype: dict
     @return: a copy of the passed in ndparams with missing keys filled
-        from the cluster defaults
+        from the node group defaults
 
     """
     return FillDict(self.ndparams, ndparams)
@@ -1041,6 +1071,7 @@ class Cluster(TaggableObject):
     "master_netdev",
     "cluster_name",
     "file_storage_dir",
+    "shared_file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
     "os_hvp",
@@ -1064,7 +1095,7 @@ class Cluster(TaggableObject):
     """Fill defaults for missing configuration values.
 
     """
-    # pylint: disable-msg=E0203
+    # pylint: disable=E0203
     # because these are "defined" via slots, not manually
     if self.hvparams is None:
       self.hvparams = constants.HVC_DEFAULTS
@@ -1098,7 +1129,7 @@ class Cluster(TaggableObject):
     if self.modify_ssh_setup is None:
       self.modify_ssh_setup = True
 
-    # default_bridge is no longer used it 2.1. The slot is left there to
+    # default_bridge is no longer used in 2.1. The slot is left there to
     # support auto-upgrading. It can be removed once we decide to deprecate
     # upgrading straight from 2.0.
     if self.default_bridge is not None:
@@ -1140,6 +1171,10 @@ class Cluster(TaggableObject):
     if self.prealloc_wipe_disks is None:
       self.prealloc_wipe_disks = False
 
+    # shared_file_storage_dir added before 2.5
+    if self.shared_file_storage_dir is None:
+      self.shared_file_storage_dir = ""
+
   def ToDict(self):
     """Custom function for cluster.
 
@@ -1277,7 +1312,7 @@ class Cluster(TaggableObject):
     return FillDict(result, os_params)
 
   def FillND(self, node, nodegroup):
-    """Return filled out ndparams for L{objects.NodeGroup} and L{object.Node}
+    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
 
     @type node: L{objects.Node}
     @param node: A Node object to fill
@@ -1337,6 +1372,7 @@ class ImportExportOptions(ConfigObject):
   @ivar compress: Compression method (one of L{constants.IEC_ALL})
   @ivar magic: Used to ensure the connection goes to the right disk
   @ivar ipv6: Whether to use IPv6
+  @ivar connect_timeout: Number of seconds for establishing connection
 
   """
   __slots__ = [
@@ -1345,6 +1381,7 @@ class ImportExportOptions(ConfigObject):
     "compress",
     "magic",
     "ipv6",
+    "connect_timeout",
     ]
 
 
@@ -1385,17 +1422,124 @@ class ConfdReply(ConfigObject):
 class QueryFieldDefinition(ConfigObject):
   """Object holding a query field definition.
 
-  @ivar name: Field name as a regular expression
+  @ivar name: Field name
   @ivar title: Human-readable title
   @ivar kind: Field type
+  @ivar doc: Human-readable description
 
   """
   __slots__ = [
     "name",
     "title",
     "kind",
+    "doc",
+    ]
+
+
+class _QueryResponseBase(ConfigObject):
+  __slots__ = [
+    "fields",
+    ]
+
+  def ToDict(self):
+    """Custom function for serializing.
+
+    """
+    mydict = super(_QueryResponseBase, self).ToDict()
+    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
+    return mydict
+
+  @classmethod
+  def FromDict(cls, val):
+    """Custom function for de-serializing.
+
+    """
+    obj = super(_QueryResponseBase, cls).FromDict(val)
+    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
+    return obj
+
+
+class QueryRequest(ConfigObject):
+  """Object holding a query request.
+
+  """
+  __slots__ = [
+    "what",
+    "fields",
+    "filter",
+    ]
+
+
+class QueryResponse(_QueryResponseBase):
+  """Object holding the response to a query.
+
+  @ivar fields: List of L{QueryFieldDefinition} objects
+  @ivar data: Requested data
+
+  """
+  __slots__ = [
+    "data",
+    ]
+
+
+class QueryFieldsRequest(ConfigObject):
+  """Object holding a request for querying available fields.
+
+  """
+  __slots__ = [
+    "what",
+    "fields",
+    ]
+
+
+class QueryFieldsResponse(_QueryResponseBase):
+  """Object holding the response to a query for fields.
+
+  @ivar fields: List of L{QueryFieldDefinition} objects
+
+  """
+  __slots__ = [
+    ]
+
+
+class InstanceConsole(ConfigObject):
+  """Object describing how to access the console of an instance.
+
+  """
+  __slots__ = [
+    "instance",
+    "kind",
+    "message",
+    "host",
+    "port",
+    "user",
+    "command",
+    "display",
     ]
 
+  def Validate(self):
+    """Validates contents of this object.
+
+    """
+    assert self.kind in constants.CONS_ALL, "Unknown console type"
+    assert self.instance, "Missing instance name"
+    assert self.message or self.kind in [constants.CONS_SSH,
+                                         constants.CONS_SPICE,
+                                         constants.CONS_VNC]
+    assert self.host or self.kind == constants.CONS_MESSAGE
+    assert self.port or self.kind in [constants.CONS_MESSAGE,
+                                      constants.CONS_SSH]
+    assert self.user or self.kind in [constants.CONS_MESSAGE,
+                                      constants.CONS_SPICE,
+                                      constants.CONS_VNC]
+    assert self.command or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_SPICE,
+                                         constants.CONS_VNC]
+    assert self.display or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_SPICE,
+                                         constants.CONS_SSH]
+    return True
+
 
 class SerializableConfigParser(ConfigParser.SafeConfigParser):
   """Simple wrapper over ConfigParse that allows serialization.