diff --git a/Makefile.am b/Makefile.am index 35d1e5dd072ca980202b3e9204b6ce3ab11fee7c..58b18697e55d1b6e56eefd3acfc5c2d65c596bce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -311,7 +311,8 @@ cmdlib_PYTHON = \ lib/cmdlib/__init__.py \ lib/cmdlib/common.py \ lib/cmdlib/base.py \ - lib/cmdlib/tags.py + lib/cmdlib/tags.py \ + lib/cmdlib/network.py hypervisor_PYTHON = \ lib/hypervisor/__init__.py \ diff --git a/lib/cmdlib/__init__.py b/lib/cmdlib/__init__.py index c3148af47ce50645b636968f6e3003bfab44e304..88aa0012c52f1c92ba79755c0e8f97a20412afb6 100644 --- a/lib/cmdlib/__init__.py +++ b/lib/cmdlib/__init__.py @@ -66,8 +66,11 @@ from ganeti.masterd import iallocator from ganeti.cmdlib.base import ResultWithJobs, LogicalUnit, NoHooksLU, \ Tasklet, _QueryBase from ganeti.cmdlib.common import _ExpandInstanceName, _ExpandItemName, \ - _ExpandNodeName, _ShareAll + _ExpandNodeName, _ShareAll, _CheckNodeGroupInstances from ganeti.cmdlib.tags import LUTagsGet, LUTagsSearch, LUTagsSet, LUTagsDel +from ganeti.cmdlib.network import LUNetworkAdd, LUNetworkRemove, \ + LUNetworkSetParams, _NetworkQuery, LUNetworkQuery, LUNetworkConnect, \ + LUNetworkDisconnect import ganeti.masterd.instance # pylint: disable=W0611 @@ -153,30 +156,6 @@ def _CheckInstanceNodeGroups(cfg, instance_name, owned_groups, return inst_groups -def _CheckNodeGroupInstances(cfg, group_uuid, owned_instances): - """Checks if the instances in a node group are still correct. - - @type cfg: L{config.ConfigWriter} - @param cfg: The cluster configuration - @type group_uuid: string - @param group_uuid: Node group UUID - @type owned_instances: set or frozenset - @param owned_instances: List of currently owned instances - - """ - wanted_instances = cfg.GetNodeGroupInstances(group_uuid) - if owned_instances != wanted_instances: - raise errors.OpPrereqError("Instances in node group '%s' changed since" - " locks were acquired, wanted '%s', have '%s';" - " retry the operation" % - (group_uuid, - utils.CommaJoin(wanted_instances), - utils.CommaJoin(owned_instances)), - errors.ECODE_STATE) - - return wanted_instances - - def _SupportsOob(cfg, node): """Tells if node supports OOB. @@ -898,47 +877,6 @@ def _ComputeNewInstanceViolations(old_ipolicy, new_ipolicy, instances, cfg): _ComputeViolatingInstances(old_ipolicy, instances, cfg)) -def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6, - mac_prefix, tags): - """Builds network related env variables for hooks - - This builds the hook environment from individual variables. - - @type name: string - @param name: the name of the network - @type subnet: string - @param subnet: the ipv4 subnet - @type gateway: string - @param gateway: the ipv4 gateway - @type network6: string - @param network6: the ipv6 subnet - @type gateway6: string - @param gateway6: the ipv6 gateway - @type mac_prefix: string - @param mac_prefix: the mac_prefix - @type tags: list - @param tags: the tags of the network - - """ - env = {} - if name: - env["NETWORK_NAME"] = name - if subnet: - env["NETWORK_SUBNET"] = subnet - if gateway: - env["NETWORK_GATEWAY"] = gateway - if network6: - env["NETWORK_SUBNET6"] = network6 - if gateway6: - env["NETWORK_GATEWAY6"] = gateway6 - if mac_prefix: - env["NETWORK_MAC_PREFIX"] = mac_prefix - if tags: - env["NETWORK_TAGS"] = " ".join(tags) - - return env - - def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, minmem, maxmem, vcpus, nics, disk_template, disks, bep, hvp, hypervisor_name, tags): @@ -15715,650 +15653,6 @@ class LUTestAllocator(NoHooksLU): return result -class LUNetworkAdd(LogicalUnit): - """Logical unit for creating networks. - - """ - HPATH = "network-add" - HTYPE = constants.HTYPE_NETWORK - REQ_BGL = False - - def BuildHooksNodes(self): - """Build hooks nodes. - - """ - mn = self.cfg.GetMasterNode() - return ([mn], [mn]) - - def CheckArguments(self): - if self.op.mac_prefix: - self.op.mac_prefix = \ - utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix) - - def ExpandNames(self): - self.network_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId()) - - if self.op.conflicts_check: - self.share_locks[locking.LEVEL_NODE] = 1 - self.share_locks[locking.LEVEL_NODE_ALLOC] = 1 - self.needed_locks = { - locking.LEVEL_NODE: locking.ALL_SET, - locking.LEVEL_NODE_ALLOC: locking.ALL_SET, - } - else: - self.needed_locks = {} - - self.add_locks[locking.LEVEL_NETWORK] = self.network_uuid - - def CheckPrereq(self): - if self.op.network is None: - raise errors.OpPrereqError("Network must be given", - errors.ECODE_INVAL) - - try: - existing_uuid = self.cfg.LookupNetwork(self.op.network_name) - except errors.OpPrereqError: - pass - else: - raise errors.OpPrereqError("Desired network name '%s' already exists as a" - " network (UUID: %s)" % - (self.op.network_name, existing_uuid), - errors.ECODE_EXISTS) - - # Check tag validity - for tag in self.op.tags: - objects.TaggableObject.ValidateTag(tag) - - def BuildHooksEnv(self): - """Build hooks env. - - """ - args = { - "name": self.op.network_name, - "subnet": self.op.network, - "gateway": self.op.gateway, - "network6": self.op.network6, - "gateway6": self.op.gateway6, - "mac_prefix": self.op.mac_prefix, - "tags": self.op.tags, - } - return _BuildNetworkHookEnv(**args) # pylint: disable=W0142 - - 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, - uuid=self.network_uuid) - # Initialize the associated address pool - try: - pool = network.AddressPool.InitializeNetwork(nobj) - except errors.AddressPoolError, err: - raise errors.OpExecError("Cannot create IP address pool for network" - " '%s': %s" % (self.op.network_name, err)) - - # 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. - if self.op.conflicts_check: - for node in self.cfg.GetAllNodesInfo().values(): - for ip in [node.primary_ip, node.secondary_ip]: - try: - if pool.Contains(ip): - pool.Reserve(ip) - self.LogInfo("Reserved IP address of node '%s' (%s)", - node.name, ip) - except errors.AddressPoolError, err: - self.LogWarning("Cannot reserve IP address '%s' of node '%s': %s", - ip, node.name, err) - - master_ip = self.cfg.GetClusterInfo().master_ip - try: - if pool.Contains(master_ip): - pool.Reserve(master_ip) - self.LogInfo("Reserved cluster master IP address (%s)", master_ip) - except errors.AddressPoolError, err: - self.LogWarning("Cannot reserve cluster master IP address (%s): %s", - master_ip, err) - - if self.op.add_reserved_ips: - for ip in self.op.add_reserved_ips: - try: - pool.Reserve(ip, external=True) - except errors.AddressPoolError, err: - raise errors.OpExecError("Cannot reserve IP address '%s': %s" % - (ip, err)) - - if self.op.tags: - for tag in self.op.tags: - nobj.AddTag(tag) - - self.cfg.AddNetwork(nobj, self.proc.GetECId(), check_uuid=False) - del self.remove_locks[locking.LEVEL_NETWORK] - - -class LUNetworkRemove(LogicalUnit): - HPATH = "network-remove" - HTYPE = constants.HTYPE_NETWORK - REQ_BGL = False - - def ExpandNames(self): - self.network_uuid = self.cfg.LookupNetwork(self.op.network_name) - - self.share_locks[locking.LEVEL_NODEGROUP] = 1 - self.needed_locks = { - locking.LEVEL_NETWORK: [self.network_uuid], - locking.LEVEL_NODEGROUP: locking.ALL_SET, - } - - 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. - - """ - # Verify that the network is not conncted. - node_groups = [group.name - for group in self.cfg.GetAllNodeGroupsInfo().values() - if self.network_uuid in group.networks] - - if node_groups: - self.LogWarning("Network '%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): - """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): - """Modifies the parameters of a network. - - """ - HPATH = "network-modify" - HTYPE = constants.HTYPE_NETWORK - REQ_BGL = False - - def CheckArguments(self): - if (self.op.gateway and - (self.op.add_reserved_ips or self.op.remove_reserved_ips)): - raise errors.OpPrereqError("Cannot modify gateway and reserved ips" - " at once", errors.ECODE_INVAL) - - 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. - - """ - self.network = self.cfg.GetNetwork(self.network_uuid) - self.gateway = self.network.gateway - self.mac_prefix = self.network.mac_prefix - self.network6 = self.network.network6 - self.gateway6 = self.network.gateway6 - self.tags = self.network.tags - - self.pool = network.AddressPool(self.network) - - if self.op.gateway: - if self.op.gateway == constants.VALUE_NONE: - self.gateway = None - else: - self.gateway = self.op.gateway - if self.pool.IsReserved(self.gateway): - raise errors.OpPrereqError("Gateway IP address '%s' is already" - " reserved" % self.gateway, - errors.ECODE_STATE) - - if self.op.mac_prefix: - if self.op.mac_prefix == constants.VALUE_NONE: - self.mac_prefix = None - else: - self.mac_prefix = \ - utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix) - - if self.op.gateway6: - if self.op.gateway6 == constants.VALUE_NONE: - self.gateway6 = None - else: - self.gateway6 = self.op.gateway6 - - if self.op.network6: - if self.op.network6 == constants.VALUE_NONE: - self.network6 = None - else: - self.network6 = self.op.network6 - - def BuildHooksEnv(self): - """Build hooks env. - - """ - args = { - "name": self.op.network_name, - "subnet": self.network.network, - "gateway": self.gateway, - "network6": self.network6, - "gateway6": self.gateway6, - "mac_prefix": self.mac_prefix, - "tags": self.tags, - } - return _BuildNetworkHookEnv(**args) # pylint: disable=W0142 - - def BuildHooksNodes(self): - """Build hooks nodes. - - """ - mn = self.cfg.GetMasterNode() - return ([mn], [mn]) - - def Exec(self, feedback_fn): - """Modifies the network. - - """ - #TODO: reserve/release via temporary reservation manager - # extend cfg.ReserveIp/ReleaseIp with the external flag - if self.op.gateway: - if self.gateway == self.network.gateway: - self.LogWarning("Gateway is already %s", self.gateway) - else: - if self.gateway: - self.pool.Reserve(self.gateway, external=True) - if self.network.gateway: - self.pool.Release(self.network.gateway, external=True) - self.network.gateway = self.gateway - - if self.op.add_reserved_ips: - for ip in self.op.add_reserved_ips: - try: - if self.pool.IsReserved(ip): - self.LogWarning("IP address %s is already reserved", ip) - else: - self.pool.Reserve(ip, external=True) - except errors.AddressPoolError, err: - self.LogWarning("Cannot reserve IP address %s: %s", ip, err) - - if self.op.remove_reserved_ips: - for ip in self.op.remove_reserved_ips: - if ip == self.network.gateway: - self.LogWarning("Cannot unreserve Gateway's IP") - continue - try: - if not self.pool.IsReserved(ip): - self.LogWarning("IP address %s is already unreserved", ip) - else: - self.pool.Release(ip, external=True) - except errors.AddressPoolError, err: - self.LogWarning("Cannot release IP address %s: %s", ip, err) - - if self.op.mac_prefix: - self.network.mac_prefix = self.mac_prefix - - if self.op.network6: - self.network.network6 = self.network6 - - if self.op.gateway6: - self.network.gateway6 = self.gateway6 - - self.pool.Validate() - - self.cfg.Update(self.network, feedback_fn) - - -class _NetworkQuery(_QueryBase): - FIELDS = query.NETWORK_FIELDS - - def ExpandNames(self, lu): - lu.needed_locks = {} - lu.share_locks = _ShareAll() - - self.do_locking = self.use_locking - - all_networks = lu.cfg.GetAllNetworksInfo() - name_to_uuid = dict((n.name, n.uuid) for n in all_networks.values()) - - if self.names: - missing = [] - self.wanted = [] - - for name in self.names: - if name in name_to_uuid: - self.wanted.append(name_to_uuid[name]) - else: - missing.append(name) - - if missing: - raise errors.OpPrereqError("Some networks do not exist: %s" % missing, - errors.ECODE_NOENT) - else: - self.wanted = locking.ALL_SET - - if self.do_locking: - lu.needed_locks[locking.LEVEL_NETWORK] = self.wanted - if query.NETQ_INST in self.requested_data: - lu.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET - if query.NETQ_GROUP in self.requested_data: - lu.needed_locks[locking.LEVEL_NODEGROUP] = locking.ALL_SET - - def DeclareLocks(self, lu, level): - pass - - def _GetQueryData(self, lu): - """Computes the list of networks and their attributes. - - """ - all_networks = lu.cfg.GetAllNetworksInfo() - - network_uuids = self._GetNames(lu, all_networks.keys(), - locking.LEVEL_NETWORK) - - do_instances = query.NETQ_INST in self.requested_data - do_groups = query.NETQ_GROUP in self.requested_data - - network_to_instances = None - network_to_groups = None - - # For NETQ_GROUP, we need to map network->[groups] - if do_groups: - all_groups = lu.cfg.GetAllNodeGroupsInfo() - network_to_groups = dict((uuid, []) for uuid in network_uuids) - for _, group in all_groups.iteritems(): - for net_uuid in network_uuids: - netparams = group.networks.get(net_uuid, None) - if netparams: - info = (group.name, netparams[constants.NIC_MODE], - netparams[constants.NIC_LINK]) - - network_to_groups[net_uuid].append(info) - - if do_instances: - all_instances = lu.cfg.GetAllInstancesInfo() - network_to_instances = dict((uuid, []) for uuid in network_uuids) - for instance in all_instances.values(): - for nic in instance.nics: - if nic.network in network_uuids: - network_to_instances[nic.network].append(instance.name) - break - - if query.NETQ_STATS in self.requested_data: - stats = \ - dict((uuid, - self._GetStats(network.AddressPool(all_networks[uuid]))) - for uuid in network_uuids) - else: - stats = None - - return query.NetworkQueryData([all_networks[uuid] - for uuid in network_uuids], - network_to_groups, - network_to_instances, - stats) - - @staticmethod - def _GetStats(pool): - """Returns statistics for a network address pool. - - """ - return { - "free_count": pool.GetFreeCount(), - "reserved_count": pool.GetReservedCount(), - "map": pool.GetMap(), - "external_reservations": - utils.CommaJoin(pool.GetExternalReservations()), - } - - -class LUNetworkQuery(NoHooksLU): - """Logical unit for querying networks. - - """ - REQ_BGL = False - - def CheckArguments(self): - self.nq = _NetworkQuery(qlang.MakeSimpleFilter("name", self.op.names), - self.op.output_fields, self.op.use_locking) - - def ExpandNames(self): - self.nq.ExpandNames(self) - - def Exec(self, feedback_fn): - return self.nq.OldStyleQuery(self) - - -class LUNetworkConnect(LogicalUnit): - """Connect a network to a nodegroup - - """ - HPATH = "network-connect" - HTYPE = constants.HTYPE_NETWORK - REQ_BGL = False - - def ExpandNames(self): - self.network_name = self.op.network_name - self.group_name = self.op.group_name - self.network_mode = self.op.network_mode - self.network_link = self.op.network_link - - self.network_uuid = self.cfg.LookupNetwork(self.network_name) - self.group_uuid = self.cfg.LookupNodeGroup(self.group_name) - - self.needed_locks = { - locking.LEVEL_INSTANCE: [], - locking.LEVEL_NODEGROUP: [self.group_uuid], - } - self.share_locks[locking.LEVEL_INSTANCE] = 1 - - if self.op.conflicts_check: - self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid] - self.share_locks[locking.LEVEL_NETWORK] = 1 - - def DeclareLocks(self, level): - if level == locking.LEVEL_INSTANCE: - assert not self.needed_locks[locking.LEVEL_INSTANCE] - - # Lock instances optimistically, needs verification once group lock has - # been acquired - if self.op.conflicts_check: - self.needed_locks[locking.LEVEL_INSTANCE] = \ - self.cfg.GetNodeGroupInstances(self.group_uuid) - - def BuildHooksEnv(self): - ret = { - "GROUP_NAME": self.group_name, - "GROUP_NETWORK_MODE": self.network_mode, - "GROUP_NETWORK_LINK": self.network_link, - } - return ret - - def BuildHooksNodes(self): - nodes = self.cfg.GetNodeGroup(self.group_uuid).members - return (nodes, nodes) - - def CheckPrereq(self): - owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) - - assert self.group_uuid in owned_groups - - # Check if locked instances are still correct - owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) - if self.op.conflicts_check: - _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances) - - self.netparams = { - constants.NIC_MODE: self.network_mode, - constants.NIC_LINK: self.network_link, - } - objects.NIC.CheckParameterSyntax(self.netparams) - - self.group = self.cfg.GetNodeGroup(self.group_uuid) - #if self.network_mode == constants.NIC_MODE_BRIDGED: - # _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid) - self.connected = False - if self.network_uuid in self.group.networks: - self.LogWarning("Network '%s' is already mapped to group '%s'" % - (self.network_name, self.group.name)) - self.connected = True - - # check only if not already connected - elif self.op.conflicts_check: - pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid)) - - _NetworkConflictCheck(self, lambda nic: pool.Contains(nic.ip), - "connect to", owned_instances) - - def Exec(self, feedback_fn): - # Connect the network and update the group only if not already connected - if not self.connected: - self.group.networks[self.network_uuid] = self.netparams - self.cfg.Update(self.group, feedback_fn) - - -def _NetworkConflictCheck(lu, check_fn, action, instances): - """Checks for network interface conflicts with a network. - - @type lu: L{LogicalUnit} - @type check_fn: callable receiving one parameter (L{objects.NIC}) and - returning boolean - @param check_fn: Function checking for conflict - @type action: string - @param action: Part of error message (see code) - @raise errors.OpPrereqError: If conflicting IP addresses are found. - - """ - conflicts = [] - - for (_, instance) in lu.cfg.GetMultiInstanceInfo(instances): - instconflicts = [(idx, nic.ip) - for (idx, nic) in enumerate(instance.nics) - if check_fn(nic)] - - if instconflicts: - conflicts.append((instance.name, instconflicts)) - - if conflicts: - lu.LogWarning("IP addresses from network '%s', which is about to %s" - " node group '%s', are in use: %s" % - (lu.network_name, action, lu.group.name, - utils.CommaJoin(("%s: %s" % - (name, _FmtNetworkConflict(details))) - for (name, details) in conflicts))) - - raise errors.OpPrereqError("Conflicting IP addresses found; " - " remove/modify the corresponding network" - " interfaces", errors.ECODE_STATE) - - -def _FmtNetworkConflict(details): - """Utility for L{_NetworkConflictCheck}. - - """ - return utils.CommaJoin("nic%s/%s" % (idx, ipaddr) - for (idx, ipaddr) in details) - - -class LUNetworkDisconnect(LogicalUnit): - """Disconnect a network to a nodegroup - - """ - HPATH = "network-disconnect" - HTYPE = constants.HTYPE_NETWORK - REQ_BGL = False - - def ExpandNames(self): - self.network_name = self.op.network_name - self.group_name = self.op.group_name - - self.network_uuid = self.cfg.LookupNetwork(self.network_name) - self.group_uuid = self.cfg.LookupNodeGroup(self.group_name) - - self.needed_locks = { - locking.LEVEL_INSTANCE: [], - locking.LEVEL_NODEGROUP: [self.group_uuid], - } - self.share_locks[locking.LEVEL_INSTANCE] = 1 - - def DeclareLocks(self, level): - if level == locking.LEVEL_INSTANCE: - assert not self.needed_locks[locking.LEVEL_INSTANCE] - - # Lock instances optimistically, needs verification once group lock has - # been acquired - self.needed_locks[locking.LEVEL_INSTANCE] = \ - self.cfg.GetNodeGroupInstances(self.group_uuid) - - def BuildHooksEnv(self): - ret = { - "GROUP_NAME": self.group_name, - } - return ret - - def BuildHooksNodes(self): - nodes = self.cfg.GetNodeGroup(self.group_uuid).members - return (nodes, nodes) - - def CheckPrereq(self): - owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) - - assert self.group_uuid in owned_groups - - # Check if locked instances are still correct - owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) - _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances) - - self.group = self.cfg.GetNodeGroup(self.group_uuid) - self.connected = True - if self.network_uuid not in self.group.networks: - self.LogWarning("Network '%s' is not mapped to group '%s'", - self.network_name, self.group.name) - self.connected = False - - # We need this check only if network is not already connected - else: - _NetworkConflictCheck(self, lambda nic: nic.network == self.network_uuid, - "disconnect from", owned_instances) - - def Exec(self, feedback_fn): - # Disconnect the network and update the group only if network is connected - if self.connected: - del self.group.networks[self.network_uuid] - self.cfg.Update(self.group, feedback_fn) - - #: Query type implementations _QUERY_IMPL = { constants.QR_CLUSTER: _ClusterQuery, diff --git a/lib/cmdlib/common.py b/lib/cmdlib/common.py index 60d5fe5abd30be1fb1c0a90f1af841357f6bdd4b..f3bd9e4ab30aa8be45e7b1f54c456054c0a78907 100644 --- a/lib/cmdlib/common.py +++ b/lib/cmdlib/common.py @@ -23,6 +23,7 @@ from ganeti import errors from ganeti import locking +from ganeti import utils def _ExpandItemName(fn, name, kind): @@ -57,3 +58,27 @@ def _ShareAll(): """ return dict.fromkeys(locking.LEVELS, 1) + + +def _CheckNodeGroupInstances(cfg, group_uuid, owned_instances): + """Checks if the instances in a node group are still correct. + + @type cfg: L{config.ConfigWriter} + @param cfg: The cluster configuration + @type group_uuid: string + @param group_uuid: Node group UUID + @type owned_instances: set or frozenset + @param owned_instances: List of currently owned instances + + """ + wanted_instances = cfg.GetNodeGroupInstances(group_uuid) + if owned_instances != wanted_instances: + raise errors.OpPrereqError("Instances in node group '%s' changed since" + " locks were acquired, wanted '%s', have '%s';" + " retry the operation" % + (group_uuid, + utils.CommaJoin(wanted_instances), + utils.CommaJoin(owned_instances)), + errors.ECODE_STATE) + + return wanted_instances diff --git a/lib/cmdlib/network.py b/lib/cmdlib/network.py new file mode 100644 index 0000000000000000000000000000000000000000..0488cebe7f6cdb3bcf5a308f3439cc7999fe77d8 --- /dev/null +++ b/lib/cmdlib/network.py @@ -0,0 +1,718 @@ +# +# + +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + + +"""Logical units dealing with networks.""" + +from ganeti import constants +from ganeti import errors +from ganeti import locking +from ganeti import network +from ganeti import objects +from ganeti import qlang +from ganeti import query +from ganeti import utils +from ganeti.cmdlib.base import LogicalUnit, NoHooksLU, _QueryBase +from ganeti.cmdlib.common import _ShareAll, _CheckNodeGroupInstances + + +def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6, + mac_prefix, tags): + """Builds network related env variables for hooks + + This builds the hook environment from individual variables. + + @type name: string + @param name: the name of the network + @type subnet: string + @param subnet: the ipv4 subnet + @type gateway: string + @param gateway: the ipv4 gateway + @type network6: string + @param network6: the ipv6 subnet + @type gateway6: string + @param gateway6: the ipv6 gateway + @type mac_prefix: string + @param mac_prefix: the mac_prefix + @type tags: list + @param tags: the tags of the network + + """ + env = {} + if name: + env["NETWORK_NAME"] = name + if subnet: + env["NETWORK_SUBNET"] = subnet + if gateway: + env["NETWORK_GATEWAY"] = gateway + if network6: + env["NETWORK_SUBNET6"] = network6 + if gateway6: + env["NETWORK_GATEWAY6"] = gateway6 + if mac_prefix: + env["NETWORK_MAC_PREFIX"] = mac_prefix + if tags: + env["NETWORK_TAGS"] = " ".join(tags) + + return env + + +class LUNetworkAdd(LogicalUnit): + """Logical unit for creating networks. + + """ + HPATH = "network-add" + HTYPE = constants.HTYPE_NETWORK + REQ_BGL = False + + def BuildHooksNodes(self): + """Build hooks nodes. + + """ + mn = self.cfg.GetMasterNode() + return ([mn], [mn]) + + def CheckArguments(self): + if self.op.mac_prefix: + self.op.mac_prefix = \ + utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix) + + def ExpandNames(self): + self.network_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId()) + + if self.op.conflicts_check: + self.share_locks[locking.LEVEL_NODE] = 1 + self.share_locks[locking.LEVEL_NODE_ALLOC] = 1 + self.needed_locks = { + locking.LEVEL_NODE: locking.ALL_SET, + locking.LEVEL_NODE_ALLOC: locking.ALL_SET, + } + else: + self.needed_locks = {} + + self.add_locks[locking.LEVEL_NETWORK] = self.network_uuid + + def CheckPrereq(self): + if self.op.network is None: + raise errors.OpPrereqError("Network must be given", + errors.ECODE_INVAL) + + try: + existing_uuid = self.cfg.LookupNetwork(self.op.network_name) + except errors.OpPrereqError: + pass + else: + raise errors.OpPrereqError("Desired network name '%s' already exists as a" + " network (UUID: %s)" % + (self.op.network_name, existing_uuid), + errors.ECODE_EXISTS) + + # Check tag validity + for tag in self.op.tags: + objects.TaggableObject.ValidateTag(tag) + + def BuildHooksEnv(self): + """Build hooks env. + + """ + args = { + "name": self.op.network_name, + "subnet": self.op.network, + "gateway": self.op.gateway, + "network6": self.op.network6, + "gateway6": self.op.gateway6, + "mac_prefix": self.op.mac_prefix, + "tags": self.op.tags, + } + return _BuildNetworkHookEnv(**args) # pylint: disable=W0142 + + 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, + uuid=self.network_uuid) + # Initialize the associated address pool + try: + pool = network.AddressPool.InitializeNetwork(nobj) + except errors.AddressPoolError, err: + raise errors.OpExecError("Cannot create IP address pool for network" + " '%s': %s" % (self.op.network_name, err)) + + # 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. + if self.op.conflicts_check: + for node in self.cfg.GetAllNodesInfo().values(): + for ip in [node.primary_ip, node.secondary_ip]: + try: + if pool.Contains(ip): + pool.Reserve(ip) + self.LogInfo("Reserved IP address of node '%s' (%s)", + node.name, ip) + except errors.AddressPoolError, err: + self.LogWarning("Cannot reserve IP address '%s' of node '%s': %s", + ip, node.name, err) + + master_ip = self.cfg.GetClusterInfo().master_ip + try: + if pool.Contains(master_ip): + pool.Reserve(master_ip) + self.LogInfo("Reserved cluster master IP address (%s)", master_ip) + except errors.AddressPoolError, err: + self.LogWarning("Cannot reserve cluster master IP address (%s): %s", + master_ip, err) + + if self.op.add_reserved_ips: + for ip in self.op.add_reserved_ips: + try: + pool.Reserve(ip, external=True) + except errors.AddressPoolError, err: + raise errors.OpExecError("Cannot reserve IP address '%s': %s" % + (ip, err)) + + if self.op.tags: + for tag in self.op.tags: + nobj.AddTag(tag) + + self.cfg.AddNetwork(nobj, self.proc.GetECId(), check_uuid=False) + del self.remove_locks[locking.LEVEL_NETWORK] + + +class LUNetworkRemove(LogicalUnit): + HPATH = "network-remove" + HTYPE = constants.HTYPE_NETWORK + REQ_BGL = False + + def ExpandNames(self): + self.network_uuid = self.cfg.LookupNetwork(self.op.network_name) + + self.share_locks[locking.LEVEL_NODEGROUP] = 1 + self.needed_locks = { + locking.LEVEL_NETWORK: [self.network_uuid], + locking.LEVEL_NODEGROUP: locking.ALL_SET, + } + + 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. + + """ + # Verify that the network is not conncted. + node_groups = [group.name + for group in self.cfg.GetAllNodeGroupsInfo().values() + if self.network_uuid in group.networks] + + if node_groups: + self.LogWarning("Network '%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): + """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): + """Modifies the parameters of a network. + + """ + HPATH = "network-modify" + HTYPE = constants.HTYPE_NETWORK + REQ_BGL = False + + def CheckArguments(self): + if (self.op.gateway and + (self.op.add_reserved_ips or self.op.remove_reserved_ips)): + raise errors.OpPrereqError("Cannot modify gateway and reserved ips" + " at once", errors.ECODE_INVAL) + + 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. + + """ + self.network = self.cfg.GetNetwork(self.network_uuid) + self.gateway = self.network.gateway + self.mac_prefix = self.network.mac_prefix + self.network6 = self.network.network6 + self.gateway6 = self.network.gateway6 + self.tags = self.network.tags + + self.pool = network.AddressPool(self.network) + + if self.op.gateway: + if self.op.gateway == constants.VALUE_NONE: + self.gateway = None + else: + self.gateway = self.op.gateway + if self.pool.IsReserved(self.gateway): + raise errors.OpPrereqError("Gateway IP address '%s' is already" + " reserved" % self.gateway, + errors.ECODE_STATE) + + if self.op.mac_prefix: + if self.op.mac_prefix == constants.VALUE_NONE: + self.mac_prefix = None + else: + self.mac_prefix = \ + utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix) + + if self.op.gateway6: + if self.op.gateway6 == constants.VALUE_NONE: + self.gateway6 = None + else: + self.gateway6 = self.op.gateway6 + + if self.op.network6: + if self.op.network6 == constants.VALUE_NONE: + self.network6 = None + else: + self.network6 = self.op.network6 + + def BuildHooksEnv(self): + """Build hooks env. + + """ + args = { + "name": self.op.network_name, + "subnet": self.network.network, + "gateway": self.gateway, + "network6": self.network6, + "gateway6": self.gateway6, + "mac_prefix": self.mac_prefix, + "tags": self.tags, + } + return _BuildNetworkHookEnv(**args) # pylint: disable=W0142 + + def BuildHooksNodes(self): + """Build hooks nodes. + + """ + mn = self.cfg.GetMasterNode() + return ([mn], [mn]) + + def Exec(self, feedback_fn): + """Modifies the network. + + """ + #TODO: reserve/release via temporary reservation manager + # extend cfg.ReserveIp/ReleaseIp with the external flag + if self.op.gateway: + if self.gateway == self.network.gateway: + self.LogWarning("Gateway is already %s", self.gateway) + else: + if self.gateway: + self.pool.Reserve(self.gateway, external=True) + if self.network.gateway: + self.pool.Release(self.network.gateway, external=True) + self.network.gateway = self.gateway + + if self.op.add_reserved_ips: + for ip in self.op.add_reserved_ips: + try: + if self.pool.IsReserved(ip): + self.LogWarning("IP address %s is already reserved", ip) + else: + self.pool.Reserve(ip, external=True) + except errors.AddressPoolError, err: + self.LogWarning("Cannot reserve IP address %s: %s", ip, err) + + if self.op.remove_reserved_ips: + for ip in self.op.remove_reserved_ips: + if ip == self.network.gateway: + self.LogWarning("Cannot unreserve Gateway's IP") + continue + try: + if not self.pool.IsReserved(ip): + self.LogWarning("IP address %s is already unreserved", ip) + else: + self.pool.Release(ip, external=True) + except errors.AddressPoolError, err: + self.LogWarning("Cannot release IP address %s: %s", ip, err) + + if self.op.mac_prefix: + self.network.mac_prefix = self.mac_prefix + + if self.op.network6: + self.network.network6 = self.network6 + + if self.op.gateway6: + self.network.gateway6 = self.gateway6 + + self.pool.Validate() + + self.cfg.Update(self.network, feedback_fn) + + +class _NetworkQuery(_QueryBase): + FIELDS = query.NETWORK_FIELDS + + def ExpandNames(self, lu): + lu.needed_locks = {} + lu.share_locks = _ShareAll() + + self.do_locking = self.use_locking + + all_networks = lu.cfg.GetAllNetworksInfo() + name_to_uuid = dict((n.name, n.uuid) for n in all_networks.values()) + + if self.names: + missing = [] + self.wanted = [] + + for name in self.names: + if name in name_to_uuid: + self.wanted.append(name_to_uuid[name]) + else: + missing.append(name) + + if missing: + raise errors.OpPrereqError("Some networks do not exist: %s" % missing, + errors.ECODE_NOENT) + else: + self.wanted = locking.ALL_SET + + if self.do_locking: + lu.needed_locks[locking.LEVEL_NETWORK] = self.wanted + if query.NETQ_INST in self.requested_data: + lu.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET + if query.NETQ_GROUP in self.requested_data: + lu.needed_locks[locking.LEVEL_NODEGROUP] = locking.ALL_SET + + def DeclareLocks(self, lu, level): + pass + + def _GetQueryData(self, lu): + """Computes the list of networks and their attributes. + + """ + all_networks = lu.cfg.GetAllNetworksInfo() + + network_uuids = self._GetNames(lu, all_networks.keys(), + locking.LEVEL_NETWORK) + + do_instances = query.NETQ_INST in self.requested_data + do_groups = query.NETQ_GROUP in self.requested_data + + network_to_instances = None + network_to_groups = None + + # For NETQ_GROUP, we need to map network->[groups] + if do_groups: + all_groups = lu.cfg.GetAllNodeGroupsInfo() + network_to_groups = dict((uuid, []) for uuid in network_uuids) + for _, group in all_groups.iteritems(): + for net_uuid in network_uuids: + netparams = group.networks.get(net_uuid, None) + if netparams: + info = (group.name, netparams[constants.NIC_MODE], + netparams[constants.NIC_LINK]) + + network_to_groups[net_uuid].append(info) + + if do_instances: + all_instances = lu.cfg.GetAllInstancesInfo() + network_to_instances = dict((uuid, []) for uuid in network_uuids) + for instance in all_instances.values(): + for nic in instance.nics: + if nic.network in network_uuids: + network_to_instances[nic.network].append(instance.name) + break + + if query.NETQ_STATS in self.requested_data: + stats = \ + dict((uuid, + self._GetStats(network.AddressPool(all_networks[uuid]))) + for uuid in network_uuids) + else: + stats = None + + return query.NetworkQueryData([all_networks[uuid] + for uuid in network_uuids], + network_to_groups, + network_to_instances, + stats) + + @staticmethod + def _GetStats(pool): + """Returns statistics for a network address pool. + + """ + return { + "free_count": pool.GetFreeCount(), + "reserved_count": pool.GetReservedCount(), + "map": pool.GetMap(), + "external_reservations": + utils.CommaJoin(pool.GetExternalReservations()), + } + + +class LUNetworkQuery(NoHooksLU): + """Logical unit for querying networks. + + """ + REQ_BGL = False + + def CheckArguments(self): + self.nq = _NetworkQuery(qlang.MakeSimpleFilter("name", self.op.names), + self.op.output_fields, self.op.use_locking) + + def ExpandNames(self): + self.nq.ExpandNames(self) + + def Exec(self, feedback_fn): + return self.nq.OldStyleQuery(self) + + +def _FmtNetworkConflict(details): + """Utility for L{_NetworkConflictCheck}. + + """ + return utils.CommaJoin("nic%s/%s" % (idx, ipaddr) + for (idx, ipaddr) in details) + + +def _NetworkConflictCheck(lu, check_fn, action, instances): + """Checks for network interface conflicts with a network. + + @type lu: L{LogicalUnit} + @type check_fn: callable receiving one parameter (L{objects.NIC}) and + returning boolean + @param check_fn: Function checking for conflict + @type action: string + @param action: Part of error message (see code) + @raise errors.OpPrereqError: If conflicting IP addresses are found. + + """ + conflicts = [] + + for (_, instance) in lu.cfg.GetMultiInstanceInfo(instances): + instconflicts = [(idx, nic.ip) + for (idx, nic) in enumerate(instance.nics) + if check_fn(nic)] + + if instconflicts: + conflicts.append((instance.name, instconflicts)) + + if conflicts: + lu.LogWarning("IP addresses from network '%s', which is about to %s" + " node group '%s', are in use: %s" % + (lu.network_name, action, lu.group.name, + utils.CommaJoin(("%s: %s" % + (name, _FmtNetworkConflict(details))) + for (name, details) in conflicts))) + + raise errors.OpPrereqError("Conflicting IP addresses found; " + " remove/modify the corresponding network" + " interfaces", errors.ECODE_STATE) + + +class LUNetworkConnect(LogicalUnit): + """Connect a network to a nodegroup + + """ + HPATH = "network-connect" + HTYPE = constants.HTYPE_NETWORK + REQ_BGL = False + + def ExpandNames(self): + self.network_name = self.op.network_name + self.group_name = self.op.group_name + self.network_mode = self.op.network_mode + self.network_link = self.op.network_link + + self.network_uuid = self.cfg.LookupNetwork(self.network_name) + self.group_uuid = self.cfg.LookupNodeGroup(self.group_name) + + self.needed_locks = { + locking.LEVEL_INSTANCE: [], + locking.LEVEL_NODEGROUP: [self.group_uuid], + } + self.share_locks[locking.LEVEL_INSTANCE] = 1 + + if self.op.conflicts_check: + self.needed_locks[locking.LEVEL_NETWORK] = [self.network_uuid] + self.share_locks[locking.LEVEL_NETWORK] = 1 + + def DeclareLocks(self, level): + if level == locking.LEVEL_INSTANCE: + assert not self.needed_locks[locking.LEVEL_INSTANCE] + + # Lock instances optimistically, needs verification once group lock has + # been acquired + if self.op.conflicts_check: + self.needed_locks[locking.LEVEL_INSTANCE] = \ + self.cfg.GetNodeGroupInstances(self.group_uuid) + + def BuildHooksEnv(self): + ret = { + "GROUP_NAME": self.group_name, + "GROUP_NETWORK_MODE": self.network_mode, + "GROUP_NETWORK_LINK": self.network_link, + } + return ret + + def BuildHooksNodes(self): + nodes = self.cfg.GetNodeGroup(self.group_uuid).members + return (nodes, nodes) + + def CheckPrereq(self): + owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) + + assert self.group_uuid in owned_groups + + # Check if locked instances are still correct + owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) + if self.op.conflicts_check: + _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances) + + self.netparams = { + constants.NIC_MODE: self.network_mode, + constants.NIC_LINK: self.network_link, + } + objects.NIC.CheckParameterSyntax(self.netparams) + + self.group = self.cfg.GetNodeGroup(self.group_uuid) + #if self.network_mode == constants.NIC_MODE_BRIDGED: + # _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid) + self.connected = False + if self.network_uuid in self.group.networks: + self.LogWarning("Network '%s' is already mapped to group '%s'" % + (self.network_name, self.group.name)) + self.connected = True + + # check only if not already connected + elif self.op.conflicts_check: + pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid)) + + _NetworkConflictCheck(self, lambda nic: pool.Contains(nic.ip), + "connect to", owned_instances) + + def Exec(self, feedback_fn): + # Connect the network and update the group only if not already connected + if not self.connected: + self.group.networks[self.network_uuid] = self.netparams + self.cfg.Update(self.group, feedback_fn) + + +class LUNetworkDisconnect(LogicalUnit): + """Disconnect a network to a nodegroup + + """ + HPATH = "network-disconnect" + HTYPE = constants.HTYPE_NETWORK + REQ_BGL = False + + def ExpandNames(self): + self.network_name = self.op.network_name + self.group_name = self.op.group_name + + self.network_uuid = self.cfg.LookupNetwork(self.network_name) + self.group_uuid = self.cfg.LookupNodeGroup(self.group_name) + + self.needed_locks = { + locking.LEVEL_INSTANCE: [], + locking.LEVEL_NODEGROUP: [self.group_uuid], + } + self.share_locks[locking.LEVEL_INSTANCE] = 1 + + def DeclareLocks(self, level): + if level == locking.LEVEL_INSTANCE: + assert not self.needed_locks[locking.LEVEL_INSTANCE] + + # Lock instances optimistically, needs verification once group lock has + # been acquired + self.needed_locks[locking.LEVEL_INSTANCE] = \ + self.cfg.GetNodeGroupInstances(self.group_uuid) + + def BuildHooksEnv(self): + ret = { + "GROUP_NAME": self.group_name, + } + return ret + + def BuildHooksNodes(self): + nodes = self.cfg.GetNodeGroup(self.group_uuid).members + return (nodes, nodes) + + def CheckPrereq(self): + owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) + + assert self.group_uuid in owned_groups + + # Check if locked instances are still correct + owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) + _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances) + + self.group = self.cfg.GetNodeGroup(self.group_uuid) + self.connected = True + if self.network_uuid not in self.group.networks: + self.LogWarning("Network '%s' is not mapped to group '%s'", + self.network_name, self.group.name) + self.connected = False + + # We need this check only if network is not already connected + else: + _NetworkConflictCheck(self, lambda nic: nic.network == self.network_uuid, + "disconnect from", owned_instances) + + def Exec(self, feedback_fn): + # Disconnect the network and update the group only if network is connected + if self.connected: + del self.group.networks[self.network_uuid] + self.cfg.Update(self.group, feedback_fn)