diff --git a/lib/config.py b/lib/config.py index 499fa54944abf4816f95ec3541bd7e50b8bb9130..b58d4eec10623f085471f3adfbd55c2977d0b873 100644 --- a/lib/config.py +++ b/lib/config.py @@ -49,7 +49,7 @@ from ganeti import serializer from ganeti import uidpool from ganeti import netutils from ganeti import runtime -from ganeti import ippool +from ganeti import network _config_lock = locking.SharedLock("ConfigWriter") @@ -217,102 +217,135 @@ class ConfigWriter: else: self._temporary_macs.Reserve(ec_id, mac) - def _UnlockedCommitIp(self, link, address): + def _UnlockedCommitIp(self, net_uuid, address): """Commit a reserved IP address to an IP pool. The IP address is taken from the IP pool designated by link and marked as reserved. """ - if link is None: - nicparams = self._config_data.cluster.nicparams[constants.VALUE_DEFAULT] - link = nicparams[constants.NIC_LINK] + nobj = self._UnlockedGetNetwork(net_uuid) + pool = network.AddressPool(nobj) + pool.Reserve(address) - if link not in self._config_data.cluster.networks or\ - "4" not in self._config_data.cluster.networks[link]: - return False + @locking.ssynchronized(_config_lock) + def CommitIp(self, net_uuid, address): + """Commit a reserved IP to an IP pool - netdict = self._config_data.cluster.networks[link]["4"] - network = ippool.IPv4Network.fromdict(netdict) - try: - addr = network.reserve(address) - except ippool.IPv4PoolError, err: - raise errors.ConfigurationError("Unable to reserve IP: %s", str(err)) + This is just a wrapper around _UnlockedCommitIp. - self._config_data.cluster.networks[link]["4"] = network.todict() + @param net_uuid: UUID of the network to commit the IP to + @param address: The IP address + + """ + self._UnlockedCommitIp(net_uuid, address) + self._WriteConfig() @locking.ssynchronized(_config_lock) - def CommitIp(self, link, address): - """Commit a reserved IP to an IP pool + def CommitGroupInstanceIps(self, group_uuid, net_uuid, + link, feedback_fn=None): + """Commit all IPs of instances on a given node group's link to the pools. - This is just a wrapper around _UnlockedCommitIp. + This is used when mapping networks to node groups, and is a separate method + to ensure atomicity (i.e. all or none commited). + + @param group_uuid: the uuid of the node group + @param net_uuid: the uuid of the network to use + @param link: the link on which the relevant instances reside """ - self._UnlockedCommitIp(self, link) + affected_nodes = [] + for node, ni in self._config_data.nodes.items(): + if ni.group == group_uuid: + affected_nodes.append(node) + + for instance in self._config_data.instances.values(): + if instance.primary_node not in affected_nodes: + continue + + for nic in instance.nics: + nic_link = nic.nicparams.get(constants.NIC_LINK, None) + if nic_link == link: + if feedback_fn: + feedback_fn("Commiting instance %s IP %s" % (instance.name, nic.ip)) + self._UnlockedCommitIp(net_uuid, nic.ip) + self._WriteConfig() - def _UnlockedReleaseIp(self, link, address): + def _UnlockedReleaseIp(self, net_uuid, address): """Give a specific IP address back to an IP pool. The IP address is returned to the IP pool designated by pool_id and marked as reserved. """ - if link is None: - nicparams = self._config_data.cluster.nicparams[constants.VALUE_DEFAULT] - link = nicparams[constants.NIC_LINK] - - if link not in self._config_data.cluster.networks or\ - "4" not in self._config_data.cluster.networks[link]: - return - - netdict = self._config_data.cluster.networks[link]["4"] - network = ippool.IPv4Network.fromdict(netdict) - network.release(address) - self._config_data.cluster.networks[link]["4"] = network.todict() + nobj = self._UnlockedGetNetwork(net_uuid) + pool = network.AddressPool(nobj) + pool.Release(address) @locking.ssynchronized(_config_lock) - def ReleaseIp(self, link, address): + def ReleaseIp(self, net_uuid, address): """Give a specified IP address back to an IP pool. This is just a wrapper around _UnlockedReleaseIp. """ - self._UnlockedReleaseIp(link, address) + self._UnlockedReleaseIp(net_uuid, address) + self._WriteConfig() + + @locking.ssynchronized(_config_lock) + def ReleaseGroupInstanceIps(self, group_uuid, net_uuid, + link, feedback_fn=None): + """Commit all IPs of instances on a given node group's link to the pools. + + This is used when unmapping networks from node groups and + is a separate method to ensure atomicity (i.e. all or none commited). + + @param group_uuid: the uuid of the node group + @param net_uuid: the uuid of the network to use + @param link: the link on which the relevant instances reside + + """ + affected_nodes = [] + for node, ni in self._config_data.nodes.items(): + if ni.group == group_uuid: + affected_nodes.append(node) + + for instance in self._config_data.instances.values(): + if instance.primary_node not in affected_nodes: + continue + + for nic in instance.nics: + nic_link = nic.nicparams.get(constants.NIC_LINK, None) + if nic_link == link: + if feedback_fn: + feedback_fn("Releasing instance %s IP %s" % (instance.name, nic.ip)) + self._UnlockedReleaseIp(net_uuid, nic.ip) + self._WriteConfig() @locking.ssynchronized(_config_lock, shared=1) - def GenerateIp(self, link, ec_id): + def GenerateIp(self, node_name, link, ec_id): """Find a free IPv4 address for an instance. """ - if link is None: - nicparams = self._config_data.cluster.nicparams[constants.VALUE_DEFAULT] - link = nicparams[constants.NIC_LINK] - - if link not in self._config_data.cluster.networks or\ - "4" not in self._config_data.cluster.networks[link]: - raise errors.OpPrereqError("No network defined on link %s exists" % link, - errors.ECODE_INVAL) - netdict = self._config_data.cluster.networks[link]["4"] - network = ippool.IPv4Network.fromdict(netdict) - return self._temporary_ips.Generate([], network.generate_free(), ec_id) + net_uuid = self._UnlockedGetNetworkFromNodeLink(node_name, link) + nobj = self._UnlockedGetNetwork(net_uuid) + pool = network.AddressPool(nobj) + return self._temporary_ips.Generate([], pool.GenerateFree(), ec_id) @locking.ssynchronized(_config_lock, shared=1) - def ReserveIp(self, link, address, ec_id): + def ReserveIp(self, node_name, link, address, ec_id): """Reserve a given IPv4 address for use by an instance. """ - if link is None: - nicparams = self._config_data.cluster.nicparams[constants.VALUE_DEFAULT] - link = nicparams[constants.NIC_LINK] - - if link not in self._config_data.cluster.networks or\ - "4" not in self._config_data.cluster.networks[link]: - return - netdict = self._config_data.cluster.networks[link]["4"] - network = ippool.IPv4Network.fromdict(netdict) - network.reserve(address) + net_uuid = self._UnlockedGetNetworkFromNodeLink(node_name, link) + nobj = self._UnlockedGetNetwork(net_uuid) + pool = network.AddressPool(nobj) + try: + pool.Reserve(address) + except errors.AddressPoolError: + raise errors.ReservationError("IP address already in use") return self._temporary_ips.Reserve(address, ec_id) @locking.ssynchronized(_config_lock, shared=1) @@ -1165,6 +1198,19 @@ class ConfigWriter: """ return self._config_data.nodegroups.keys() + def _UnlockedGetNetworkFromNodeLink(self, node_name, node_link): + node = self._config_data.nodes[node_name] + nodegroup = self._UnlockedGetNodeGroup(node.group) + for uuid, link in nodegroup.networks.items(): + if link == node_link: + return uuid + + return None + + @locking.ssynchronized(_config_lock, shared=1) + def GetNetworkFromNodeLink(self, node_name, node_link): + return self._UnlockedGetNetworkFromNodeLink(node_name, node_link) + @locking.ssynchronized(_config_lock) def AddInstance(self, instance, ec_id): """Add an instance to the config. @@ -1859,6 +1905,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, @@ -1882,6 +1931,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))] @@ -1983,6 +2033,8 @@ class ConfigWriter: test = target in self._config_data.instances.values() elif isinstance(target, objects.NodeGroup): test = target in self._config_data.nodegroups.values() + elif isinstance(target, objects.Network): + test = target in self._config_data.networks.values() else: raise errors.ProgrammerError("Invalid object type (%s) passed to" " ConfigWriter.Update" % type(target)) @@ -2009,3 +2061,118 @@ 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() + + 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): + """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) + self._WriteConfig() + + def _UnlockedAddNetwork(self, net, ec_id): + """Add a network to the configuration. + + """ + logging.info("Adding network %s to configuration", net.name) + + self._EnsureUUID(net, ec_id) + + try: + existing_uuid = self._UnlockedLookupNetwork(net.name) + except errors.OpPrereqError: + pass + else: + raise errors.OpPrereqError("Desired network name '%s' already exists as a" + " network (UUID: %s)" % + (net.name, existing_uuid), + errors.ECODE_EXISTS) + + 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 + raise errors.OpPrereqError("Network '%s' not found" % target, + errors.ECODE_NOENT) + + @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) diff --git a/lib/constants.py b/lib/constants.py index 85e6dcd2379a30e9962f6f46b2ee8b1f1bd0714d..7ca9afd96d1c9a2cb696cd45b13da43a0d1961d7 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -1100,6 +1100,7 @@ SS_HYPERVISOR_LIST = "hypervisor_list" SS_MAINTAIN_NODE_HEALTH = "maintain_node_health" SS_UID_POOL = "uid_pool" SS_NODEGROUPS = "nodegroups" +SS_NETWORKS = "networks" # cluster wide default parameters DEFAULT_ENABLED_HYPERVISOR = HT_XEN_PVM diff --git a/lib/ssconf.py b/lib/ssconf.py index 5498ba361d4fe1daf2c3ad64be7a2b662bedcc2a..6d6e3d7d79e97b7ca09652c3efdfd83be7e6e3a5 100644 --- a/lib/ssconf.py +++ b/lib/ssconf.py @@ -293,6 +293,7 @@ class SimpleStore(object): constants.SS_MAINTAIN_NODE_HEALTH, constants.SS_UID_POOL, constants.SS_NODEGROUPS, + constants.SS_NETWORKS, ) _MAX_SIZE = 131072 @@ -445,6 +446,14 @@ class SimpleStore(object): nl = data.splitlines(False) return nl + def GetNetworkList(self): + """Return the list of networks. + + """ + data = self._ReadFile(constants.SS_NETWORKS) + nl = data.splitlines(False) + return nl + def GetClusterTags(self): """Return the cluster tags.