diff --git a/lib/bootstrap.py b/lib/bootstrap.py index 5a784b92cf9aca6b08f0e8f41b2ca6fadf3a0762..0944fb76b80b2a8d6f300da5142cf10909b60043 100644 --- a/lib/bootstrap.py +++ b/lib/bootstrap.py @@ -603,6 +603,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 aaf1c64edd21d4f1e857d2fe57b7bfcf478483ca..86797ce24a875f7ee2dea406dff72f3574b07a4b 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -15301,6 +15301,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 = { @@ -15308,6 +15363,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 5f2b24a49d45dd837fdbbd4dad7af1c88d64b05a..fb657e91f630219575bbb4472ee96b07ab6b9eb7 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -302,6 +302,7 @@ HTYPE_CLUSTER = "CLUSTER" HTYPE_NODE = "NODE" HTYPE_GROUP = "GROUP" HTYPE_INSTANCE = "INSTANCE" +HTYPE_NETWORK = "NETWORK" HKR_SKIP = 0 HKR_FAIL = 1 @@ -1065,9 +1066,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, @@ -1095,11 +1104,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()) @@ -1603,6 +1614,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([ @@ -1612,6 +1624,7 @@ QR_VIA_OP = frozenset([ QR_GROUP, QR_OS, QR_EXPORT, + QR_NETWORK, ]) #: List of resources which can be queried using Local UniX Interface @@ -1703,6 +1716,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 4be25ab37969c4e66e9a7d58d48b74f7f76dd972..a31b2a54269ca0fab8c9c24fde0a92b36b82c237 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -51,7 +51,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"] @@ -432,6 +432,7 @@ class ConfigData(ConfigObject): "nodes", "nodegroups", "instances", + "networks", "serial_no", ] + _TIMESTAMPS @@ -444,7 +445,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 @@ -459,6 +460,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): @@ -495,11 +497,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): @@ -1359,6 +1363,7 @@ class NodeGroup(TaggableObject): "hv_state_static", "disk_state_static", "alloc_policy", + "networks", ] + _TIMESTAMPS + _UUID def ToDict(self): @@ -1406,6 +1411,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} @@ -1989,6 +1997,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 d73388e40a43a3ae7dcf7984506d545dc87e3cfa..21f2db326e7c46c5c9c35e83aa419abc7831e89c 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 @@ -167,6 +168,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])") @@ -343,6 +347,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(objectutils.AutoSlots): """Meta class for opcode definitions. @@ -1201,6 +1250,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), @@ -1594,6 +1644,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 @@ -1952,6 +2003,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.