commit 176b0ee24b9d16e3805bfc6035402b1cdea3ca75
Author: Dimitris Aragiorgis <dimara@grnet.gr>
Date:   Mon Jun 4 16:50:42 2012 +0300

    IP pool related objects, opcodes and constants
    
    Config objects:
     * Introduce L{Network} with
      - IPv4 network field (mandatory)
      - IPv4 gateway, IPv6 (network/gateway), mac prefix, type (optional)
     * Modify existing config objects to support networks:
      - Add new slot 'network' to L{NIC} config object
      - Add new slot 'networks' to L{NodeGroup} config object
    
    Opcodes:
     * Introduce new opcodes for networks
      - add/remove/modify/query/connect/disconnect.
     * In InstanceCreate/InstanceSetParams add conflicts_check option
    
    Constants:
     * INIC_PARAM 'INIC_NETWORK'
     * NIC_IP_POOL for automaticaly obtain an IP from a pool
     * NETWORK_TYPE_PUBLIC/PRIVATE for network types
    
    Checking of network_type handled by the opcode parameter validation.
    Introduce _CheckCIDR*Notation() functions for network parameters validation.
    
    Signed-off-by: Apollon Oikonomopoulos <apollon@noc.grnet.gr>
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

diff --git a/lib/bootstrap.py b/lib/bootstrap.py
index 440f568..c5d9e5e 100644
--- a/lib/bootstrap.py
+++ b/lib/bootstrap.py
@@ -600,6 +600,7 @@ def InitConfig(version, cluster_config, master_node_config,
                                    nodegroups=nodegroups,
                                    nodes=nodes,
                                    instances={},
+                                   networks={},
                                    serial_no=1,
                                    ctime=now, mtime=now)
   utils.WriteFile(cfg_file,
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 652a940..eb55193 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -15340,6 +15340,61 @@ class LUTestAllocator(NoHooksLU):
       result = ial.out_text
     return result
 
+# Network LUs
+class LUNetworkAdd(LogicalUnit):
+  def BuildHooksNodes(self):
+    pass
+
+  def BuildHooksEnv(self):
+    pass
+
+
+class LUNetworkRemove(LogicalUnit):
+  def BuildHooksNodes(self):
+    pass
+
+  def BuildHooksEnv(self):
+    pass
+
+
+class LUNetworkSetParams(LogicalUnit):
+  def BuildHooksNodes(self):
+    pass
+
+  def BuildHooksEnv(self):
+    pass
+
+
+class _NetworkQuery(_QueryBase):
+  def ExpandNames(self, lu):
+    pass
+
+  def DeclareLocks(self, lu, level):
+    pass
+
+  def _GetQueryData(self, lu):
+    pass
+
+
+class LUNetworkQuery(NoHooksLU):
+  pass
+
+
+class LUNetworkConnect(LogicalUnit):
+  def BuildHooksNodes(self):
+    pass
+
+  def BuildHooksEnv(self):
+    pass
+
+
+class LUNetworkDisconnect(LogicalUnit):
+  def BuildHooksNodes(self):
+    pass
+
+  def BuildHooksEnv(self):
+    pass
+
 
 #: Query type implementations
 _QUERY_IMPL = {
@@ -15347,6 +15402,7 @@ _QUERY_IMPL = {
   constants.QR_INSTANCE: _InstanceQuery,
   constants.QR_NODE: _NodeQuery,
   constants.QR_GROUP: _GroupQuery,
+  constants.QR_NETWORK: _NetworkQuery,
   constants.QR_OS: _OsQuery,
   constants.QR_EXPORT: _ExportQuery,
   }
diff --git a/lib/constants.py b/lib/constants.py
index 540eb1f..297a4d0 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -369,6 +369,7 @@ HTYPE_CLUSTER = "CLUSTER"
 HTYPE_NODE = "NODE"
 HTYPE_GROUP = "GROUP"
 HTYPE_INSTANCE = "INSTANCE"
+HTYPE_NETWORK = "NETWORK"
 
 HKR_SKIP = 0
 HKR_FAIL = 1
@@ -1091,9 +1092,17 @@ NIC_LINK = "link"
 
 NIC_MODE_BRIDGED = "bridged"
 NIC_MODE_ROUTED = "routed"
+NIC_IP_POOL = "pool"
 
 NIC_VALID_MODES = frozenset([NIC_MODE_BRIDGED, NIC_MODE_ROUTED])
 
+# An extra description of the network.
+# Can be used by hooks/kvm-vif-bridge to apply different rules
+NETWORK_TYPE_PRIVATE = "private"
+NETWORK_TYPE_PUBLIC = "public"
+
+NETWORK_VALID_TYPES = frozenset([NETWORK_TYPE_PRIVATE, NETWORK_TYPE_PUBLIC])
+
 NICS_PARAMETER_TYPES = {
   NIC_MODE: VTYPE_STRING,
   NIC_LINK: VTYPE_STRING,
@@ -1121,11 +1130,13 @@ INIC_MAC = "mac"
 INIC_IP = "ip"
 INIC_MODE = "mode"
 INIC_LINK = "link"
+INIC_NETWORK = "network"
 INIC_PARAMS_TYPES = {
   INIC_IP: VTYPE_MAYBE_STRING,
   INIC_LINK: VTYPE_STRING,
   INIC_MAC: VTYPE_STRING,
   INIC_MODE: VTYPE_STRING,
+  INIC_NETWORK: VTYPE_MAYBE_STRING,
   }
 INIC_PARAMS = frozenset(INIC_PARAMS_TYPES.keys())
 
@@ -1626,6 +1637,7 @@ QR_GROUP = "group"
 QR_OS = "os"
 QR_JOB = "job"
 QR_EXPORT = "export"
+QR_NETWORK = "network"
 
 #: List of resources which can be queried using L{opcodes.OpQuery}
 QR_VIA_OP = frozenset([
@@ -1635,6 +1647,7 @@ QR_VIA_OP = frozenset([
   QR_GROUP,
   QR_OS,
   QR_EXPORT,
+  QR_NETWORK,
   ])
 
 #: List of resources which can be queried using Local UniX Interface
@@ -1726,6 +1739,7 @@ SS_HYPERVISOR_LIST = "hypervisor_list"
 SS_MAINTAIN_NODE_HEALTH = "maintain_node_health"
 SS_UID_POOL = "uid_pool"
 SS_NODEGROUPS = "nodegroups"
+SS_NETWORKS = "networks"
 
 SS_FILE_PERMS = 0444
 
diff --git a/lib/objects.py b/lib/objects.py
index 74526d3..e0494fc 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -50,7 +50,7 @@ from socket import AF_INET
 
 
 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
-           "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
+           "OS", "Node", "NodeGroup", "Cluster", "FillDict", "Network"]
 
 _TIMESTAMPS = ["ctime", "mtime"]
 _UUID = ["uuid"]
@@ -439,6 +439,7 @@ class ConfigData(ConfigObject):
     "nodes",
     "nodegroups",
     "instances",
+    "networks",
     "serial_no",
     ] + _TIMESTAMPS
 
@@ -451,7 +452,7 @@ class ConfigData(ConfigObject):
     """
     mydict = super(ConfigData, self).ToDict()
     mydict["cluster"] = mydict["cluster"].ToDict()
-    for key in "nodes", "instances", "nodegroups":
+    for key in "nodes", "instances", "nodegroups", "networks":
       mydict[key] = self._ContainerToDicts(mydict[key])
 
     return mydict
@@ -466,6 +467,7 @@ class ConfigData(ConfigObject):
     obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
     obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
     obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
+    obj.networks = cls._ContainerFromDicts(obj.networks, dict, Network)
     return obj
 
   def HasAnyDiskOfType(self, dev_type):
@@ -502,11 +504,13 @@ class ConfigData(ConfigObject):
       # gives a good approximation.
       if self.HasAnyDiskOfType(constants.LD_DRBD8):
         self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
+    if self.networks is None:
+      self.networks = {}
 
 
 class NIC(ConfigObject):
   """Config object representing a network card."""
-  __slots__ = ["mac", "ip", "nicparams"]
+  __slots__ = ["mac", "ip", "network", "nicparams"]
 
   @classmethod
   def CheckParameterSyntax(cls, nicparams):
@@ -1389,6 +1393,7 @@ class NodeGroup(TaggableObject):
     "hv_state_static",
     "disk_state_static",
     "alloc_policy",
+    "networks",
     ] + _TIMESTAMPS + _UUID
 
   def ToDict(self):
@@ -1436,6 +1441,9 @@ class NodeGroup(TaggableObject):
     if self.ipolicy is None:
       self.ipolicy = MakeEmptyIPolicy()
 
+    if self.networks is None:
+      self.networks = {}
+
   def FillND(self, node):
     """Return filled out ndparams for L{objects.Node}
 
@@ -2020,6 +2028,26 @@ class InstanceConsole(ConfigObject):
     return True
 
 
+class Network(ConfigObject):
+  """Object representing a network definition for ganeti.
+
+  """
+  __slots__ = [
+    "name",
+    "serial_no",
+    "network_type",
+    "mac_prefix",
+    "family",
+    "network",
+    "network6",
+    "gateway",
+    "gateway6",
+    "size",
+    "reservations",
+    "ext_reservations",
+    ] + _TIMESTAMPS + _UUID
+
+
 class SerializableConfigParser(ConfigParser.SafeConfigParser):
   """Simple wrapper over ConfigParse that allows serialization.
 
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 2a3586e..7312d13 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -35,6 +35,7 @@ opcodes.
 
 import logging
 import re
+import ipaddr
 
 from ganeti import constants
 from ganeti import errors
@@ -166,6 +167,9 @@ _PIgnoreIpolicy = ("ignore_ipolicy", False, ht.TBool,
 _PAllowRuntimeChgs = ("allow_runtime_changes", True, ht.TBool,
                       "Allow runtime changes (eg. memory ballooning)")
 
+#: a required network name
+_PNetworkName = ("network_name", ht.NoDefault, ht.TNonEmptyString,
+                 "Set network name")
 
 #: OP_ID conversion regular expression
 _OPID_RE = re.compile("([a-z])([A-Z])")
@@ -341,6 +345,51 @@ def _CheckStorageType(storage_type):
 _PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType,
                  "Storage type")
 
+_CheckNetworkType = ht.TElemOf(constants.NETWORK_VALID_TYPES)
+
+#: Network type parameter
+_PNetworkType = ("network_type", None, ht.TOr(ht.TNone, _CheckNetworkType),
+                 "Network type")
+
+def _CheckCIDRNetNotation(value):
+  """Ensure a given cidr notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv4Network(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+def _CheckCIDRAddrNotation(value):
+  """Ensure a given cidr notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv4Address(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+def _CheckCIDR6AddrNotation(value):
+  """Ensure a given cidr notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv6Address(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+def _CheckCIDR6NetNotation(value):
+  """Ensure a given cidr notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv6Network(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
 
 class _AutoOpParamSlots(type):
   """Meta class for opcode definitions.
@@ -1200,6 +1249,7 @@ class OpInstanceCreate(OpCode):
     ("identify_defaults", False, ht.TBool,
      "Reset instance parameters to default if equal"),
     ("ip_check", True, ht.TBool, _PIpCheckDoc),
+    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
     ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES),
      "Instance creation mode"),
     ("nics", ht.NoDefault, ht.TListOf(_TestNicDef),
@@ -1531,6 +1581,7 @@ class OpInstanceSetParams(OpCode):
     ("wait_for_sync", True, ht.TBool,
      "Whether to wait for the disk to synchronize, when changing template"),
     ("offline", None, ht.TMaybeBool, "Whether to mark instance as offline"),
+    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
     ]
   OP_RESULT = _TSetParamsResult
 
@@ -1887,6 +1938,88 @@ class OpTestDummy(OpCode):
   WITH_LU = False
 
 
+# Network opcodes
+# Add a new network in the cluster
+class OpNetworkAdd(OpCode):
+  """Add an IP network to the cluster."""
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PNetworkName,
+    _PNetworkType,
+    ("network", None, ht.TAnd(ht.TString ,_CheckCIDRNetNotation), None),
+    ("gateway", None, ht.TOr(ht.TNone, _CheckCIDRAddrNotation), None),
+    ("network6", None, ht.TOr(ht.TNone, _CheckCIDR6NetNotation), None),
+    ("gateway6", None, ht.TOr(ht.TNone, _CheckCIDR6AddrNotation), None),
+    ("mac_prefix", None, ht.TMaybeString, None),
+    ("add_reserved_ips", None,
+     ht.TOr(ht.TNone, ht.TListOf(_CheckCIDRAddrNotation)), None),
+    ]
+
+class OpNetworkRemove(OpCode):
+  """Remove an existing network from the cluster.
+     Must not be connected to any nodegroup.
+
+  """
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PNetworkName,
+    _PForce,
+    ]
+
+class OpNetworkSetParams(OpCode):
+  """Modify Network's parameters except for IPv4 subnet"""
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PNetworkName,
+    _PNetworkType,
+    ("gateway", None, ht.TOr(ht.TNone, _CheckCIDRAddrNotation), None),
+    ("network6", None, ht.TOr(ht.TNone, _CheckCIDR6NetNotation), None),
+    ("gateway6", None, ht.TOr(ht.TNone, _CheckCIDR6AddrNotation), None),
+    ("mac_prefix", None, ht.TMaybeString, None),
+    ("add_reserved_ips", None,
+     ht.TOr(ht.TNone, ht.TListOf(_CheckCIDRAddrNotation)), None),
+    ("remove_reserved_ips", None,
+     ht.TOr(ht.TNone, ht.TListOf(_CheckCIDRAddrNotation)), None),
+    ]
+
+class OpNetworkConnect(OpCode):
+  """Connect a Network to a specific Nodegroup with the defined netparams
+     (mode, link). Nics in this Network will inherit those params.
+     Produce errors if a NIC (that its not already assigned to a network)
+     has an IP that is contained in the Network this will produce error unless
+     --no-conflicts-check is passed.
+
+  """
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PGroupName,
+    _PNetworkName,
+    ("network_mode", None, ht.TString, None),
+    ("network_link", None, ht.TString, None),
+    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
+    ]
+
+class OpNetworkDisconnect(OpCode):
+  """Disconnect a Network from a Nodegroup. Produce errors if NICs are
+     present in the Network unless --no-conficts-check option is passed.
+
+  """
+  OP_DSC_FIELD = "network_name"
+  OP_PARAMS = [
+    _PGroupName,
+    _PNetworkName,
+    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
+    ]
+
+class OpNetworkQuery(OpCode):
+  """Compute the list of networks."""
+  OP_PARAMS = [
+    _POutputFields,
+    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
+     "Empty list to query all groups, group names otherwise"),
+    ]
+
+
 def _GetOpList():
   """Returns list of all defined opcodes.
 
