From 306bed0e647eac8386c53582a31d0972cc62bca9 Mon Sep 17 00:00:00 2001 From: Apollon Oikonomopoulos <apollon@noc.grnet.gr> Date: Mon, 4 Jun 2012 21:02:55 +0300 Subject: [PATCH] Implement LUNetworkQuery Summarily list all existing networks Supply detailed info for every existing network - List used/free IPs - List instances with NICs assigned to the corresponding network - List NIC index and IP for the above instances Implement complementary config methods for retrieving networks. Signed-off-by: Apollon Oikonomopoulos <apollon@noc.grnet.gr> Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr> Reviewed-by: Iustin Pop <iustin@google.com> --- lib/cmdlib.py | 108 +++++++++++++++++++++++++++++++++- lib/luxi.py | 4 ++ lib/query.py | 133 ++++++++++++++++++++++++++++++++++++++++++ lib/server/masterd.py | 9 +++ 4 files changed, 251 insertions(+), 3 deletions(-) diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 7546f83cc..6d42d83fa 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -15479,18 +15479,120 @@ class LUNetworkSetParams(LogicalUnit): class _NetworkQuery(_QueryBase): + FIELDS = query.NETWORK_FIELDS + def ExpandNames(self, lu): - pass + lu.needed_locks = {} + + self._all_networks = lu.cfg.GetAllNetworksInfo() + name_to_uuid = dict((n.name, n.uuid) for n in self._all_networks.values()) + + if not self.names: + self.wanted = [name_to_uuid[name] + for name in utils.NiceSort(name_to_uuid.keys())] + else: + # Accept names to be either names or UUIDs. + missing = [] + self.wanted = [] + all_uuid = frozenset(self._all_networks.keys()) + + for name in self.names: + if name in all_uuid: + self.wanted.append(name) + elif 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) def DeclareLocks(self, lu, level): pass def _GetQueryData(self, lu): - pass + """Computes the list of networks and their attributes. + + """ + do_instances = query.NETQ_INST in self.requested_data + do_groups = do_instances or (query.NETQ_GROUP in self.requested_data) + do_stats = query.NETQ_STATS in self.requested_data + cluster = lu.cfg.GetClusterInfo() + + network_to_groups = None + network_to_instances = None + stats = 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 self.wanted) + default_nicpp = cluster.nicparams[constants.PP_DEFAULT] + + if do_instances: + all_instances = lu.cfg.GetAllInstancesInfo() + all_nodes = lu.cfg.GetAllNodesInfo() + network_to_instances = dict((uuid, []) for uuid in self.wanted) + + + for group in all_groups.values(): + if do_instances: + group_nodes = [node.name for node in all_nodes.values() if + node.group == group.uuid] + group_instances = [instance for instance in all_instances.values() + if instance.primary_node in group_nodes] + + for net_uuid in group.networks.keys(): + if net_uuid in network_to_groups: + netparams = group.networks[net_uuid] + mode = netparams[constants.NIC_MODE] + link = netparams[constants.NIC_LINK] + info = group.name + '(' + mode + ', ' + link + ')' + network_to_groups[net_uuid].append(info) + + if do_instances: + for instance in group_instances: + for nic in instance.nics: + if nic.network == self._all_networks[net_uuid].name: + network_to_instances[net_uuid].append(instance.name) + break + + if do_stats: + stats = {} + for uuid, net in self._all_networks.items(): + if uuid in self.wanted: + pool = network.AddressPool(net) + stats[uuid] = { + "free_count": pool.GetFreeCount(), + "reserved_count": pool.GetReservedCount(), + "map": pool.GetMap(), + "external_reservations": ", ".join(pool.GetExternalReservations()), + } + + return query.NetworkQueryData([self._all_networks[uuid] + for uuid in self.wanted], + network_to_groups, + network_to_instances, + stats) class LUNetworkQuery(NoHooksLU): - pass + """Logical unit for querying networks. + + """ + REQ_BGL = False + + def CheckArguments(self): + self.nq = _NetworkQuery(qlang.MakeSimpleFilter("name", self.op.names), + self.op.output_fields, False) + + def ExpandNames(self): + self.nq.ExpandNames(self) + + def Exec(self, feedback_fn): + return self.nq.OldStyleQuery(self) + class LUNetworkConnect(LogicalUnit): diff --git a/lib/luxi.py b/lib/luxi.py index c32175361..2009749f0 100644 --- a/lib/luxi.py +++ b/lib/luxi.py @@ -62,6 +62,7 @@ REQ_QUERY_JOBS = "QueryJobs" REQ_QUERY_INSTANCES = "QueryInstances" REQ_QUERY_NODES = "QueryNodes" REQ_QUERY_GROUPS = "QueryGroups" +REQ_QUERY_NETWORKS = "QueryNetworks" REQ_QUERY_EXPORTS = "QueryExports" REQ_QUERY_CONFIG_VALUES = "QueryConfigValues" REQ_QUERY_CLUSTER_INFO = "QueryClusterInfo" @@ -566,6 +567,9 @@ class Client(object): def QueryGroups(self, names, fields, use_locking): return self.CallMethod(REQ_QUERY_GROUPS, (names, fields, use_locking)) + def QueryNetworks(self, names, fields, use_locking): + return self.CallMethod(REQ_QUERY_NETWORKS, (names, fields, use_locking)) + def QueryExports(self, nodes, use_locking): return self.CallMethod(REQ_QUERY_EXPORTS, (nodes, use_locking)) diff --git a/lib/query.py b/lib/query.py index d041ad5c2..08181b032 100644 --- a/lib/query.py +++ b/lib/query.py @@ -71,6 +71,10 @@ from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER, RS_NORMAL, RS_UNKNOWN, RS_NODATA, RS_UNAVAIL, RS_OFFLINE) +(NETQ_CONFIG, + NETQ_GROUP, + NETQ_STATS, + NETQ_INST) = range(300, 304) # Constants for requesting data from the caller/data provider. Each property # collected/computed separately by the data provider should have its own to @@ -2422,6 +2426,131 @@ def _BuildClusterFields(): ]) +class NetworkQueryData: + """Data container for network data queries. + + """ + def __init__(self, networks, network_to_groups, + network_to_instances, stats): + """Initializes this class. + + @param networks: List of network objects + @type network_to_groups: dict; network UUID as key + @param network_to_groups: Per-network list of groups + @type network_to_instances: dict; network UUID as key + @param network_to_instances: Per-network list of instances + @type stats: dict; network UUID as key + @param stats: Per-network usage statistics + + """ + self.networks = networks + self.network_to_groups = network_to_groups + self.network_to_instances = network_to_instances + self.stats = stats + + def __iter__(self): + """Iterate over all networks. + + """ + for net in self.networks: + if self.stats: + self.curstats = self.stats.get(net.uuid, None) + else: + self.curstats = None + yield net + + +_NETWORK_SIMPLE_FIELDS = { + "name": ("Network", QFT_TEXT, 0, "The network"), + "network": ("Subnet", QFT_TEXT, 0, "The subnet"), + "gateway": ("Gateway", QFT_OTHER, 0, "The gateway"), + "network6": ("IPv6Subnet", QFT_OTHER, 0, "The ipv6 subnet"), + "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "The ipv6 gateway"), + "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "The mac prefix"), + "network_type": ("NetworkType", QFT_OTHER, 0, "The network type"), + } + + +_NETWORK_STATS_FIELDS = { + "free_count": ("FreeCount", QFT_NUMBER, 0, "How many addresses are free"), + "reserved_count": ("ReservedCount", QFT_NUMBER, 0, "How many addresses are reserved"), + "map": ("Map", QFT_TEXT, 0, "The actual mapping"), + "external_reservations": ("ExternalReservations", QFT_TEXT, 0, "The external reservations"), + } + + +def _GetNetworkStatsField(field, kind, ctx, net): + """Gets the value of a "stats" field from L{NetworkQueryData}. + + @param field: Field name + @param kind: Data kind, one of L{constants.QFT_ALL} + @type ctx: L{NetworkQueryData} + + """ + + try: + value = ctx.curstats[field] + except KeyError: + return _FS_UNAVAIL + + if kind == QFT_TEXT: + return value + + assert kind in (QFT_NUMBER, QFT_UNIT) + + # Try to convert into number + try: + return int(value) + except (ValueError, TypeError): + logging.exception("Failed to convert network field '%s' (value %r) to int", + value, field) + return _FS_UNAVAIL + + +def _BuildNetworkFields(): + """Builds list of fields for network queries. + + """ + # Add simple fields + fields = [ + (_MakeField(name, title, kind, doc), + NETQ_CONFIG, 0, _GetItemAttr(name)) + for (name, (title, kind, flags, doc)) in _NETWORK_SIMPLE_FIELDS.items()] + + def _GetLength(getter): + return lambda ctx, network: len(getter(ctx)[network.uuid]) + + def _GetSortedList(getter): + return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid]) + + network_to_groups = operator.attrgetter("network_to_groups") + network_to_instances = operator.attrgetter("network_to_instances") + + # Add fields for node groups + fields.extend([ + (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"), + NETQ_GROUP, 0, _GetLength(network_to_groups)), + (_MakeField("group_list", "GroupList", QFT_OTHER, "List of nodegroups"), + NETQ_GROUP, 0, _GetSortedList(network_to_groups)), + ]) + + # Add fields for instances + fields.extend([ + (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"), + NETQ_INST, 0, _GetLength(network_to_instances)), + (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"), + NETQ_INST, 0, _GetSortedList(network_to_instances)), + ]) + + # Add fields for usage statistics + fields.extend([ + (_MakeField(name, title, kind, doc), NETQ_STATS, 0, + compat.partial(_GetNetworkStatsField, name, kind)) + for (name, (title, kind, flags, doc)) in _NETWORK_STATS_FIELDS.items() + ]) + + return _PrepareFieldList(fields, []) + #: Fields for cluster information CLUSTER_FIELDS = _BuildClusterFields() @@ -2446,6 +2575,9 @@ JOB_FIELDS = _BuildJobFields() #: Fields available for exports EXPORT_FIELDS = _BuildExportFields() +#: Fields available for network queries +NETWORK_FIELDS = _BuildNetworkFields() + #: All available resources ALL_FIELDS = { constants.QR_CLUSTER: CLUSTER_FIELDS, @@ -2456,6 +2588,7 @@ ALL_FIELDS = { constants.QR_OS: OS_FIELDS, constants.QR_JOB: JOB_FIELDS, constants.QR_EXPORT: EXPORT_FIELDS, + constants.QR_NETWORK: NETWORK_FIELDS, } #: All available field lists diff --git a/lib/server/masterd.py b/lib/server/masterd.py index e6916be1f..1ef5480f1 100644 --- a/lib/server/masterd.py +++ b/lib/server/masterd.py @@ -397,6 +397,15 @@ class ClientOps: op = opcodes.OpGroupQuery(names=names, output_fields=fields) return self._Query(op) + elif method == luxi.REQ_QUERY_NETWORKS: + (names, fields, use_locking) = args + logging.info("Received network query request for %s", names) + if use_locking: + raise errors.OpPrereqError("Sync queries are not allowed", + errors.ECODE_INVAL) + op = opcodes.OpNetworkQuery(names=names, output_fields=fields) + return self._Query(op) + elif method == luxi.REQ_QUERY_EXPORTS: (nodes, use_locking) = args if use_locking: -- GitLab