diff --git a/lib/bootstrap.py b/lib/bootstrap.py index 440f568a312cc04051272405b32e4faf8ddc7729..c5d9e5e79b53691aaa8a497b1e1e409554b390ee 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 15a610031d4a3da68cfef59aeb6585322be65597..9705097b350feccb6d3a8b507c60be2c57f98a65 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -15324,6 +15324,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 = { @@ -15331,6 +15386,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 02aa6f673f7875b0a840bf15002472234d7a3613..70433657f4010d2c1e16441b2746b041deb847a0 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 @@ -1089,9 +1090,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, @@ -1119,11 +1128,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()) @@ -1624,6 +1635,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([ @@ -1633,6 +1645,7 @@ QR_VIA_OP = frozenset([ QR_GROUP, QR_OS, QR_EXPORT, + QR_NETWORK, ]) #: List of resources which can be queried using Local UniX Interface @@ -1724,6 +1737,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 d79e08566e74e0c38ee5d2583bf91b15d04e4cda..2b02f9b33c87e502391ed61a8fa52a650d22a1e7 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 aa7cce1e0ade0abcec689d9070d4e53eeb284baa..7d0718a8989316146616af51c7bda589e2f5a9b6 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 @@ -162,6 +163,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])") @@ -337,6 +341,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. @@ -1195,6 +1244,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), @@ -1525,6 +1575,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 @@ -1881,6 +1932,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.