diff --git a/lib/cli.py b/lib/cli.py index f46a0a05726fbde8d09f434d55d9126359e2f149..3ba016c8163601b7db3fc87d05c1469babdf7188 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -435,6 +435,7 @@ def _ExtractTagsObject(opts, args): retval = kind, None elif kind in (constants.TAG_NODEGROUP, constants.TAG_NODE, + constants.TAG_NETWORK, constants.TAG_INSTANCE): if not args: raise errors.OpPrereqError("no arguments passed to the command", diff --git a/lib/client/gnt_network.py b/lib/client/gnt_network.py index 161059b4b180f931a424352fab6b8adf5a6d6369..207873ce5623fa47c161113dbbbb3c84b52cddd3 100644 --- a/lib/client/gnt_network.py +++ b/lib/client/gnt_network.py @@ -33,7 +33,7 @@ from textwrap import wrap #: default list of fields for L{ListNetworks} _LIST_DEF_FIELDS = ["name", "network", "gateway", - "network_type", "mac_prefix", "group_list"] + "network_type", "mac_prefix", "group_list", "tags"] def _HandleReservedIPs(ips): @@ -56,6 +56,11 @@ def AddNetwork(opts, args): """ (network_name, ) = args + if opts.tags is not None: + tags = opts.tags.split(",") + else: + tags = [] + op = opcodes.OpNetworkAdd(network_name=network_name, gateway=opts.gateway, network=opts.network, @@ -63,7 +68,8 @@ def AddNetwork(opts, args): network6=opts.network6, mac_prefix=opts.mac_prefix, network_type=opts.network_type, - add_reserved_ips=_HandleReservedIPs(opts.add_reserved_ips)) + add_reserved_ips=_HandleReservedIPs(opts.add_reserved_ips), + tags=tags) SubmitOpCode(op, opts=opts) @@ -139,6 +145,7 @@ def ListNetworks(opts, args): fmtoverride = { "group_list": (",".join, False), "inst_list": (",".join, False), + "tags": (",".join, False), } return GenericList(constants.QR_NETWORK, desired_fields, args, None, @@ -283,7 +290,7 @@ def RemoveNetwork(opts, args): commands = { "add": ( AddNetwork, ARGS_ONE_NETWORK, - [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT, + [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT, TAG_ADD_OPT, MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT], "<network_name>", "Add a new IP network to the cluster"), "list": ( @@ -322,8 +329,19 @@ commands = { RemoveNetwork, ARGS_ONE_NETWORK, [FORCE_OPT, DRY_RUN_OPT], "[--dry-run] <network_id>", "Remove an (empty) network from the cluster"), + "list-tags": ( + ListTags, ARGS_ONE_NETWORK, [], + "<network_name>", "List the tags of the given network"), + "add-tags": ( + AddTags, [ArgNetwork(min=1, max=1), ArgUnknown()], + [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT], + "<network_name> tag...", "Add tags to the given network"), + "remove-tags": ( + RemoveTags, [ArgNetwork(min=1, max=1), ArgUnknown()], + [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT], + "<network_name> tag...", "Remove tags from given network"), } def Main(): - return GenericMain(commands) + return GenericMain(commands, override={"tag_type": constants.TAG_NETWORK}) diff --git a/lib/cmdlib.py b/lib/cmdlib.py index eececc5129ce14efa37707f24e124cd9d9951fe9..5536261ca2e7af027fd6e4b734503d25b95b608f 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -1316,7 +1316,7 @@ def _ExpandInstanceName(cfg, name): return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance") def _BuildNetworkHookEnv(name, network, gateway, network6, gateway6, - network_type, mac_prefix): + network_type, mac_prefix, tags): env = dict() if name: env["NETWORK_NAME"] = name @@ -1332,6 +1332,8 @@ def _BuildNetworkHookEnv(name, network, gateway, network6, gateway6, env["NETWORK_MAC_PREFIX"] = mac_prefix if network_type: env["NETWORK_TYPE"] = network_type + if tags: + env["NETWORK_TAGS"] = " ".join(tags) return env @@ -1345,6 +1347,7 @@ def _BuildNetworkHookEnvByObject(lu, network): "gateway6": network.gateway6, "network_type": network.network_type, "mac_prefix": network.mac_prefix, + "tags" : network.tags, } return _BuildNetworkHookEnv(**args) @@ -1431,6 +1434,8 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, env["INSTANCE_NIC%d_NETWORK_MAC_PREFIX" % idx] = nobj.mac_prefix if nobj.network_type: env["INSTANCE_NIC%d_NETWORK_TYPE" % idx] = nobj.network_type + if nobj.tags: + env["INSTANCE_NIC%d_NETWORK_TAGS" % idx] = " ".join(nobj.tags) if mode == constants.NIC_MODE_BRIDGED: env["INSTANCE_NIC%d_BRIDGE" % idx] = link else: @@ -15017,6 +15022,10 @@ class TagsLU(NoHooksLU): # pylint: disable=W0223 self.group_uuid = self.cfg.LookupNodeGroup(self.op.name) lock_level = locking.LEVEL_NODEGROUP lock_name = self.group_uuid + elif self.op.kind == constants.TAG_NETWORK: + self.network_uuid = self.cfg.LookupNetwork(self.op.name) + lock_level = locking.LEVEL_NETWORK + lock_name = self.network_uuid else: lock_level = None lock_name = None @@ -15039,6 +15048,8 @@ class TagsLU(NoHooksLU): # pylint: disable=W0223 self.target = self.cfg.GetInstanceInfo(self.op.name) elif self.op.kind == constants.TAG_NODEGROUP: self.target = self.cfg.GetNodeGroup(self.group_uuid) + elif self.op.kind == constants.TAG_NETWORK: + self.target = self.cfg.GetNetwork(self.network_uuid) else: raise errors.OpPrereqError("Wrong tag type requested (%s)" % str(self.op.kind), errors.ECODE_INVAL) @@ -15549,6 +15560,11 @@ class LUNetworkAdd(LogicalUnit): if self.op.mac_prefix: utils.NormalizeAndValidateMac(self.op.mac_prefix+":00:00:00") + # Check tag validity + for tag in self.op.tags: + objects.TaggableObject.ValidateTag(tag) + + def BuildHooksEnv(self): """Build hooks env. @@ -15561,6 +15577,7 @@ class LUNetworkAdd(LogicalUnit): "gateway6": self.op.gateway6, "mac_prefix": self.op.mac_prefix, "network_type": self.op.network_type, + "tags": self.op.tags, } return _BuildNetworkHookEnv(**args) @@ -15609,6 +15626,10 @@ class LUNetworkAdd(LogicalUnit): except errors.AddressPoolError, e: raise errors.OpExecError("Cannot reserve IP %s. %s " % (ip, e)) + 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] @@ -15714,6 +15735,7 @@ class LUNetworkSetParams(LogicalUnit): 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) @@ -15765,6 +15787,7 @@ class LUNetworkSetParams(LogicalUnit): "gateway6": self.gateway6, "mac_prefix": self.mac_prefix, "network_type": self.network_type, + "tags": self.tags, } return _BuildNetworkHookEnv(**args) diff --git a/lib/constants.py b/lib/constants.py index fb657e91f630219575bbb4472ee96b07ab6b9eb7..d2825dbd3adbe5431157e0c6c060960d9c5dca18 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -538,11 +538,13 @@ TAG_CLUSTER = "cluster" TAG_NODEGROUP = "nodegroup" TAG_NODE = "node" TAG_INSTANCE = "instance" +TAG_NETWORK = "network" VALID_TAG_TYPES = frozenset([ TAG_CLUSTER, TAG_NODEGROUP, TAG_NODE, TAG_INSTANCE, + TAG_NETWORK, ]) MAX_TAG_LEN = 128 MAX_TAGS_PER_OBJ = 4096 diff --git a/lib/objects.py b/lib/objects.py index df7ac7ea8816bb73996050343ee27def0dc70e57..13378cda5ab53803ff83280134c8e0d74c45c05e 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -1997,7 +1997,7 @@ class InstanceConsole(ConfigObject): return True -class Network(ConfigObject): +class Network(TaggableObject): """Object representing a network definition for ganeti. """ diff --git a/lib/opcodes.py b/lib/opcodes.py index 21f2db326e7c46c5c9c35e83aa419abc7831e89c..9436935c7116285c8a22628605fd229d0a84e848 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -2018,6 +2018,7 @@ class OpNetworkAdd(OpCode): ("mac_prefix", None, ht.TMaybeString, None), ("add_reserved_ips", None, ht.TOr(ht.TNone, ht.TListOf(_CheckCIDRAddrNotation)), None), + ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "Network tags"), ] class OpNetworkRemove(OpCode): diff --git a/lib/query.py b/lib/query.py index b7ff5008b4af95dea6e4a6ba00b85896647af7fd..224e5b4a1dd77465227c9abcbaa26c15f9cd5799 100644 --- a/lib/query.py +++ b/lib/query.py @@ -2532,11 +2532,17 @@ def _BuildNetworkFields(): """Builds list of fields for network queries. """ - # Add simple fields fields = [ + (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0, + lambda ctx, inst: list(inst.GetTags())), + ] + + # Add simple fields + fields.extend([ (_MakeField(name, title, kind, doc), NETQ_CONFIG, 0, _GetItemAttr(name)) - for (name, (title, kind, flags, doc)) in _NETWORK_SIMPLE_FIELDS.items()] + for (name, (title, kind, flags, doc)) in _NETWORK_SIMPLE_FIELDS.items() + ]) def _GetLength(getter): return lambda ctx, network: len(getter(ctx)[network.uuid]) diff --git a/lib/rapi/client.py b/lib/rapi/client.py index 7983c09c7bdb1e5c47713558b92b9bdd5365a916..a1d729fcd20a7e3a342b5e273e4dc9cc3857197c 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -1725,7 +1725,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904 def CreateNetwork(self, network_name, network, gateway=None, network6=None, gateway6=None, mac_prefix=None, network_type=None, - add_reserved_ips=None, dry_run=False): + add_reserved_ips=None, tags=None, dry_run=False): """Creates a new network. @type name: str @@ -1740,6 +1740,12 @@ class GanetiRapiClient(object): # pylint: disable=R0904 query = [] _AppendDryRunIf(query, dry_run) + if add_reserved_ips: + add_reserved_ips = add_reserved_ips.split(',') + + if tags: + tags = tags.split(',') + body = { "network_name": network_name, "gateway": gateway, @@ -1748,7 +1754,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904 "network6": network6, "mac_prefix": mac_prefix, "network_type": network_type, - "add_reserved_ips": add_reserved_ips + "add_reserved_ips": add_reserved_ips, + "tags": tags, } return self._SendRequest(HTTP_POST, "/%s/networks" % GANETI_RAPI_VERSION, diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 584833ac07b75fb047ced2f372b1cff5c0c513bf..57fe6313cea3e0def71387f3ae1ad85ecdc2060d 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -97,7 +97,7 @@ NET_FIELDS = ["name", "network", "gateway", "mac_prefix", "network_type", "free_count", "reserved_count", "map", "group_list", "inst_list", - "external_reservations", + "external_reservations", "tags", ] G_FIELDS = [