diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 86797ce24a875f7ee2dea406dff72f3574b07a4b..7546f83cc37ad3c6b5819061930c61538ff82291 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -40,6 +40,7 @@ import tempfile import shutil import itertools import operator +import ipaddr from ganeti import ssh from ganeti import utils @@ -61,6 +62,7 @@ from ganeti import rpc from ganeti import runtime from ganeti import pathutils from ganeti import vcluster +from ganeti import network from ganeti.masterd import iallocator import ganeti.masterd.instance # pylint: disable=W0611 @@ -15303,19 +15305,169 @@ class LUTestAllocator(NoHooksLU): # Network LUs class LUNetworkAdd(LogicalUnit): + """Logical unit for creating networks. + + """ + HPATH = "network-add" + HTYPE = constants.HTYPE_NETWORK + REQ_BGL = False + def BuildHooksNodes(self): - pass + """Build hooks nodes. + + """ + mn = self.cfg.GetMasterNode() + return ([mn], [mn]) + + def ExpandNames(self): + self.network_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId()) + self.needed_locks = {} + self.add_locks[locking.LEVEL_NETWORK] = self.network_uuid + + def CheckPrereq(self): + """Check prerequisites. + + This checks that the given group name is not an existing node group + already. + + """ + if self.op.network is None: + raise errors.OpPrereqError("Network must be given", + errors.ECODE_INVAL) + + uuid = self.cfg.LookupNetwork(self.op.network_name) + + if uuid: + raise errors.OpPrereqError("Network '%s' already defined" % + self.op.network, errors.ECODE_EXISTS) + def BuildHooksEnv(self): - pass + """Build hooks env. + + """ + env = { + "NETWORK_NAME": self.op.network_name, + "NETWORK_SUBNET": self.op.network, + "NETWORK_GATEWAY": self.op.gateway, + "NETWORK_SUBNET6": self.op.network6, + "NETWORK_GATEWAY6": self.op.gateway6, + "NETWORK_MAC_PREFIX": self.op.mac_prefix, + "NETWORK_TYPE": self.op.network_type, + } + return env + + def Exec(self, feedback_fn): + """Add the ip pool to the cluster. + + """ + nobj = objects.Network(name=self.op.network_name, + network=self.op.network, + gateway=self.op.gateway, + network6=self.op.network6, + gateway6=self.op.gateway6, + mac_prefix=self.op.mac_prefix, + network_type=self.op.network_type, + uuid=self.network_uuid, + family=4) + # Initialize the associated address pool + try: + pool = network.AddressPool.InitializeNetwork(nobj) + except errors.AddressPoolError, e: + raise errors.OpExecError("Cannot create IP pool for this network. %s" % e) + + # Check if we need to reserve the nodes and the cluster master IP + # These may not be allocated to any instances in routed mode, as + # they wouldn't function anyway. + for node in self.cfg.GetAllNodesInfo().values(): + for ip in [node.primary_ip, node.secondary_ip]: + try: + pool.Reserve(ip) + self.LogInfo("Reserved node %s's IP (%s)", node.name, ip) + + except errors.AddressPoolError: + pass + + master_ip = self.cfg.GetClusterInfo().master_ip + try: + pool.Reserve(master_ip) + self.LogInfo("Reserved cluster master IP (%s)", master_ip) + except errors.AddressPoolError: + pass + + if self.op.add_reserved_ips: + for ip in self.op.add_reserved_ips: + try: + pool.Reserve(ip, external=True) + except errors.AddressPoolError, e: + raise errors.OpExecError("Cannot reserve IP %s. %s " % (ip, e)) + + self.cfg.AddNetwork(nobj, self.proc.GetECId(), check_uuid=False) + del self.remove_locks[locking.LEVEL_NETWORK] class LUNetworkRemove(LogicalUnit): - def BuildHooksNodes(self): - pass + HPATH = "network-remove" + HTYPE = constants.HTYPE_NETWORK + REQ_BGL = False + + def ExpandNames(self): + self.network_uuid = self.cfg.LookupNetwork(self.op.network_name) + + self.needed_locks = { + locking.LEVEL_NETWORK: [self.network_uuid], + } + + + def CheckPrereq(self): + """Check prerequisites. + + This checks that the given network name exists as a network, that is + empty (i.e., contains no nodes), and that is not the last group of the + cluster. + + """ + if not self.network_uuid: + raise errors.OpPrereqError("Network %s not found" % self.op.network_name, + errors.ECODE_INVAL) + + # Verify that the network is not conncted. + node_groups = [group.name + for group in self.cfg.GetAllNodeGroupsInfo().values() + for network in group.networks.keys() + if network == self.network_uuid] + + if node_groups: + self.LogWarning("Nework '%s' is connected to the following" + " node groups: %s" % (self.op.network_name, + utils.CommaJoin(utils.NiceSort(node_groups)))) + raise errors.OpPrereqError("Network still connected", + errors.ECODE_STATE) def BuildHooksEnv(self): - pass + """Build hooks env. + + """ + return { + "NETWORK_NAME": self.op.network_name, + } + + def BuildHooksNodes(self): + """Build hooks nodes. + + """ + mn = self.cfg.GetMasterNode() + return ([mn], [mn]) + + def Exec(self, feedback_fn): + """Remove the network. + + """ + try: + self.cfg.RemoveNetwork(self.network_uuid) + except errors.ConfigurationError: + raise errors.OpExecError("Network '%s' with UUID %s disappeared" % + (self.op.network_name, self.network_uuid)) class LUNetworkSetParams(LogicalUnit): diff --git a/lib/config.py b/lib/config.py index 952d1e8cc86d8e9b45700e46e946f0c6f63acf69..71781113ed26f9d51ab554d524951a58553f821b 100644 --- a/lib/config.py +++ b/lib/config.py @@ -51,6 +51,7 @@ from ganeti import uidpool from ganeti import netutils from ganeti import runtime from ganeti import pathutils +from ganeti import network _config_lock = locking.SharedLock("ConfigWriter") @@ -2094,6 +2095,9 @@ class ConfigWriter: nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in self._config_data.nodegroups.values()] nodegroups_data = fn(utils.NiceSort(nodegroups)) + networks = ["%s %s" % (net.uuid, net.name) for net in + self._config_data.networks.values()] + networks_data = fn(utils.NiceSort(networks)) ssconf_values = { constants.SS_CLUSTER_NAME: cluster.cluster_name, @@ -2118,6 +2122,7 @@ class ConfigWriter: constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health), constants.SS_UID_POOL: uid_pool, constants.SS_NODEGROUPS: nodegroups_data, + constants.SS_NETWORKS: networks_data, } bad_values = [(k, v) for k, v in ssconf_values.items() if not isinstance(v, (str, basestring))] @@ -2245,3 +2250,141 @@ class ConfigWriter: """ for rm in self._all_rms: rm.DropECReservations(ec_id) + + @locking.ssynchronized(_config_lock, shared=1) + def GetAllNetworksInfo(self): + """Get the configuration of all networks + + """ + return dict(self._config_data.networks) + + def _UnlockedGetNetworkList(self): + """Get the list of networks. + + This function is for internal use, when the config lock is already held. + + """ + return self._config_data.networks.keys() + + @locking.ssynchronized(_config_lock, shared=1) + def GetNetworkList(self): + """Get the list of networks. + + @return: array of networks, ex. ["main", "vlan100", "200] + + """ + return self._UnlockedGetNetworkList() + + @locking.ssynchronized(_config_lock, shared=1) + def GetNetworkNames(self): + """Get a list of network names + + """ + names = [network.name + for network in self._config_data.networks.values()] + return names + + def _UnlockedGetNetwork(self, uuid): + """Returns information about a network. + + This function is for internal use, when the config lock is already held. + + """ + if uuid not in self._config_data.networks: + return None + + return self._config_data.networks[uuid] + + @locking.ssynchronized(_config_lock, shared=1) + def GetNetwork(self, uuid): + """Returns information about a network. + + It takes the information from the configuration file. + + @param uuid: UUID of the network + + @rtype: L{objects.Network} + @return: the network object + + """ + return self._UnlockedGetNetwork(uuid) + + @locking.ssynchronized(_config_lock) + def AddNetwork(self, net, ec_id, check_uuid=True): + """Add a network to the configuration. + + @type net: L{objects.Network} + @param net: the Network object to add + @type ec_id: string + @param ec_id: unique id for the job to use when creating a missing UUID + + """ + self._UnlockedAddNetwork(net, ec_id, check_uuid) + self._WriteConfig() + + def _UnlockedAddNetwork(self, net, ec_id, check_uuid): + """Add a network to the configuration. + + """ + logging.info("Adding network %s to configuration", net.name) + + if check_uuid: + self._EnsureUUID(net, ec_id) + + existing_uuid = self._UnlockedLookupNetwork(net.name) + if existing_uuid: + raise errors.OpPrereqError("Desired network name '%s' already" + " exists as a network (UUID: %s)" % + (net.name, existing_uuid), + errors.ECODE_EXISTS) + net.serial_no = 1 + self._config_data.networks[net.uuid] = net + self._config_data.cluster.serial_no += 1 + + def _UnlockedLookupNetwork(self, target): + """Lookup a network's UUID. + + @type target: string + @param target: network name or UUID + @rtype: string + @return: network UUID + @raises errors.OpPrereqError: when the target network cannot be found + + """ + if target in self._config_data.networks: + return target + for net in self._config_data.networks.values(): + if net.name == target: + return net.uuid + return None + + @locking.ssynchronized(_config_lock, shared=1) + def LookupNetwork(self, target): + """Lookup a network's UUID. + + This function is just a wrapper over L{_UnlockedLookupNetwork}. + + @type target: string + @param target: network name or UUID + @rtype: string + @return: network UUID + + """ + return self._UnlockedLookupNetwork(target) + + @locking.ssynchronized(_config_lock) + def RemoveNetwork(self, network_uuid): + """Remove a network from the configuration. + + @type network_uuid: string + @param network_uuid: the UUID of the network to remove + + """ + logging.info("Removing network %s from configuration", network_uuid) + + if network_uuid not in self._config_data.networks: + raise errors.ConfigurationError("Unknown network '%s'" % network_uuid) + + del self._config_data.networks[network_uuid] + self._config_data.cluster.serial_no += 1 + self._WriteConfig() diff --git a/lib/locking.py b/lib/locking.py index cf7af34993d4dee6e9b0486b32da9341b4e4c81d..e49478534463de23e8d7ce7c87e67c1e5a1f41d5 100644 --- a/lib/locking.py +++ b/lib/locking.py @@ -1477,6 +1477,7 @@ LEVEL_NODE = 3 #: Level for node resources, used for operations with possibly high impact on #: the node's disks. LEVEL_NODE_RES = 4 +LEVEL_NETWORK = 5 LEVELS = [ LEVEL_CLUSTER, @@ -1484,6 +1485,7 @@ LEVELS = [ LEVEL_NODEGROUP, LEVEL_NODE, LEVEL_NODE_RES, + LEVEL_NETWORK, ] # Lock levels which are modifiable @@ -1492,6 +1494,7 @@ LEVELS_MOD = frozenset([ LEVEL_NODE, LEVEL_NODEGROUP, LEVEL_INSTANCE, + LEVEL_NETWORK, ]) #: Lock level names (make sure to use singular form) @@ -1501,6 +1504,7 @@ LEVEL_NAMES = { LEVEL_NODEGROUP: "nodegroup", LEVEL_NODE: "node", LEVEL_NODE_RES: "node-res", + LEVEL_NETWORK: "network", } # Constant for the big ganeti lock @@ -1518,7 +1522,7 @@ class GanetiLockManager: """ _instance = None - def __init__(self, nodes, nodegroups, instances): + def __init__(self, nodes, nodegroups, instances, networks): """Constructs a new GanetiLockManager object. There should be only a GanetiLockManager object at any time, so this @@ -1543,8 +1547,8 @@ class GanetiLockManager: LEVEL_NODE: LockSet(nodes, "node", monitor=self._monitor), LEVEL_NODE_RES: LockSet(nodes, "node-res", monitor=self._monitor), LEVEL_NODEGROUP: LockSet(nodegroups, "nodegroup", monitor=self._monitor), - LEVEL_INSTANCE: LockSet(instances, "instance", - monitor=self._monitor), + LEVEL_INSTANCE: LockSet(instances, "instance", monitor=self._monitor), + LEVEL_NETWORK: LockSet(networks, "network", monitor=self._monitor), } assert compat.all(ls.name == LEVEL_NAMES[level] diff --git a/lib/server/masterd.py b/lib/server/masterd.py index 46be85efa9f44d8d103402e33dfac5fc920196b2..e6916be1f2650e08542b0719a0bede50f94cf173 100644 --- a/lib/server/masterd.py +++ b/lib/server/masterd.py @@ -487,7 +487,8 @@ class GanetiContext(object): self.glm = locking.GanetiLockManager( self.cfg.GetNodeList(), self.cfg.GetNodeGroupList(), - self.cfg.GetInstanceList()) + self.cfg.GetInstanceList(), + self.cfg.GetNetworkList()) self.cfg.SetContext(self)