Add tags support
This patch adds the tag support for CLI. Change-Id: Ia84b873e3916e7b9668181fe14c1448ef608bf1d Partial-Implements: blueprint add-tags-to-core-resources Related-Bug: #1489291
This commit is contained in:
parent
e9560866f7
commit
f67f4af8bd
|
@ -36,6 +36,7 @@ HEX_ELEM = '[0-9A-Fa-f]'
|
|||
UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}',
|
||||
HEX_ELEM + '{4}', HEX_ELEM + '{4}',
|
||||
HEX_ELEM + '{12}'])
|
||||
HYPHEN_OPTS = ['tags_any', 'not_tags', 'not_tags_any']
|
||||
|
||||
|
||||
def _get_resource_plural(resource, client):
|
||||
|
@ -691,6 +692,8 @@ class ListCommand(NeutronCommand, lister.Lister):
|
|||
for field in self.filter_attrs]
|
||||
for attr in filter_attrs:
|
||||
val = getattr(parsed_args, attr, None)
|
||||
if attr in HYPHEN_OPTS:
|
||||
attr = attr.replace('_', '-')
|
||||
if val:
|
||||
search_opts[attr] = val
|
||||
return search_opts
|
||||
|
|
|
@ -60,6 +60,27 @@ class ListNetwork(neutronV20.ListCommand):
|
|||
{'name': 'router:external',
|
||||
'help': _('Filter and list the networks which are external.'),
|
||||
'boolean': True},
|
||||
{'name': 'tags',
|
||||
'help': _("Filter and list %s which has all given tags. "
|
||||
"Multiple tags can be set like --tags <tag[,tag...]>"),
|
||||
'boolean': False,
|
||||
'argparse_kwargs': {'metavar': 'TAG'}},
|
||||
{'name': 'tags_any',
|
||||
'help': _("Filter and list %s which has any given tags. "
|
||||
"Multiple tags can be set like --tags-any <tag[,tag...]>"),
|
||||
'boolean': False,
|
||||
'argparse_kwargs': {'metavar': 'TAG'}},
|
||||
{'name': 'not_tags',
|
||||
'help': _("Filter and list %s which does not have all given tags. "
|
||||
"Multiple tags can be set like --not-tags <tag[,tag...]>"),
|
||||
'boolean': False,
|
||||
'argparse_kwargs': {'metavar': 'TAG'}},
|
||||
{'name': 'not_tags_any',
|
||||
'help': _("Filter and list %s which does not have any given tags. "
|
||||
"Multiple tags can be set like --not-tags-any "
|
||||
"<tag[,tag...]>"),
|
||||
'boolean': False,
|
||||
'argparse_kwargs': {'metavar': 'TAG'}},
|
||||
]
|
||||
|
||||
def extend_list(self, data, parsed_args):
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutronclient._i18n import _
|
||||
from neutronclient.common import exceptions
|
||||
from neutronclient.neutron import v2_0 as neutronv20
|
||||
|
||||
|
||||
# List of resources can be set tag
|
||||
TAG_RESOURCES = ['network']
|
||||
|
||||
|
||||
def _convert_resource_args(client, parsed_args):
|
||||
resource_type = neutronv20._get_resource_plural(
|
||||
parsed_args.resource_type, client)
|
||||
resource_id = neutronv20.find_resourceid_by_name_or_id(
|
||||
client, parsed_args.resource_type, parsed_args.resource)
|
||||
return resource_type, resource_id
|
||||
|
||||
|
||||
def _add_common_arguments(parser):
|
||||
parser.add_argument('--resource-type',
|
||||
choices=TAG_RESOURCES,
|
||||
dest='resource_type',
|
||||
required=True,
|
||||
help=_('Resource Type.'))
|
||||
parser.add_argument('--resource',
|
||||
required=True,
|
||||
help=_('Resource name or ID.'))
|
||||
|
||||
|
||||
class AddTag(neutronv20.NeutronCommand):
|
||||
"""Add a tag into the resource."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(AddTag, self).get_parser(prog_name)
|
||||
_add_common_arguments(parser)
|
||||
parser.add_argument('--tag',
|
||||
required=True,
|
||||
help=_('Tag to be added.'))
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.get_client()
|
||||
resource_type, resource_id = _convert_resource_args(client,
|
||||
parsed_args)
|
||||
client.add_tag(resource_type, resource_id, parsed_args.tag)
|
||||
|
||||
|
||||
class ReplaceTag(neutronv20.NeutronCommand):
|
||||
"""Replace all tags on the resource."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ReplaceTag, self).get_parser(prog_name)
|
||||
_add_common_arguments(parser)
|
||||
parser.add_argument('--tag',
|
||||
metavar='TAG',
|
||||
action='append',
|
||||
dest='tags',
|
||||
required=True,
|
||||
help=_('Tag (This option can be repeated).'))
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.get_client()
|
||||
resource_type, resource_id = _convert_resource_args(client,
|
||||
parsed_args)
|
||||
body = {'tags': parsed_args.tags}
|
||||
client.replace_tag(resource_type, resource_id, body)
|
||||
|
||||
|
||||
class RemoveTag(neutronv20.NeutronCommand):
|
||||
"""Remove a tag on the resource."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(RemoveTag, self).get_parser(prog_name)
|
||||
_add_common_arguments(parser)
|
||||
tag_opt = parser.add_mutually_exclusive_group()
|
||||
tag_opt.add_argument('--all',
|
||||
action='store_true',
|
||||
help=_('Remove all tags on the resource.'))
|
||||
tag_opt.add_argument('--tag',
|
||||
help=_('Tag to be removed.'))
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
if not parsed_args.all and not parsed_args.tag:
|
||||
raise exceptions.CommandError(
|
||||
_("--all or --tag must be specified"))
|
||||
client = self.get_client()
|
||||
resource_type, resource_id = _convert_resource_args(client,
|
||||
parsed_args)
|
||||
if parsed_args.all:
|
||||
client.remove_tag_all(resource_type, resource_id)
|
||||
else:
|
||||
client.remove_tag(resource_type, resource_id, parsed_args.tag)
|
|
@ -83,6 +83,7 @@ from neutronclient.neutron.v2_0 import securitygroup
|
|||
from neutronclient.neutron.v2_0 import servicetype
|
||||
from neutronclient.neutron.v2_0 import subnet
|
||||
from neutronclient.neutron.v2_0 import subnetpool
|
||||
from neutronclient.neutron.v2_0 import tag
|
||||
from neutronclient.neutron.v2_0.vpn import endpoint_group
|
||||
from neutronclient.neutron.v2_0.vpn import ikepolicy
|
||||
from neutronclient.neutron.v2_0.vpn import ipsec_site_connection
|
||||
|
@ -444,6 +445,9 @@ COMMAND_V2 = {
|
|||
'bgp-peer-delete': bgp_peer.DeletePeer,
|
||||
'net-ip-availability-list': network_ip_availability.ListIpAvailability,
|
||||
'net-ip-availability-show': network_ip_availability.ShowIpAvailability,
|
||||
'tag-add': tag.AddTag,
|
||||
'tag-replace': tag.ReplaceTag,
|
||||
'tag-remove': tag.RemoveTag,
|
||||
}
|
||||
|
||||
COMMANDS = {'2.0': COMMAND_V2}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sys
|
||||
|
||||
from mox3 import mox
|
||||
|
||||
from neutronclient.common import exceptions
|
||||
from neutronclient.neutron import v2_0 as neutronV2_0
|
||||
from neutronclient.neutron.v2_0 import network
|
||||
from neutronclient.neutron.v2_0 import tag
|
||||
from neutronclient import shell
|
||||
from neutronclient.tests.unit import test_cli20
|
||||
|
||||
|
||||
class CLITestV20Tag(test_cli20.CLITestV20Base):
|
||||
def _test_tag_operation(self, cmd, path, method, args, prog_name,
|
||||
body=None):
|
||||
self.mox.StubOutWithMock(cmd, "get_client")
|
||||
self.mox.StubOutWithMock(self.client.httpclient, "request")
|
||||
cmd.get_client().MultipleTimes().AndReturn(self.client)
|
||||
if body:
|
||||
body = test_cli20.MyComparator(body, self.client)
|
||||
self.client.httpclient.request(
|
||||
test_cli20.MyUrlComparator(
|
||||
test_cli20.end_url(path, format=self.format), self.client),
|
||||
method, body=body,
|
||||
headers=mox.ContainsKeyValue(
|
||||
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(
|
||||
(test_cli20.MyResp(204), None))
|
||||
self.mox.ReplayAll()
|
||||
cmd_parser = cmd.get_parser(prog_name)
|
||||
shell.run_command(cmd, cmd_parser, args)
|
||||
self.mox.VerifyAll()
|
||||
self.mox.UnsetStubs()
|
||||
|
||||
def _test_tags_query(self, cmd, resources, args, query):
|
||||
self.mox.StubOutWithMock(cmd, "get_client")
|
||||
self.mox.StubOutWithMock(self.client.httpclient, "request")
|
||||
cmd.get_client().MultipleTimes().AndReturn(self.client)
|
||||
path = getattr(self.client, resources + "_path")
|
||||
res = {resources: [{'id': 'myid'}]}
|
||||
resstr = self.client.serialize(res)
|
||||
self.client.httpclient.request(
|
||||
test_cli20.MyUrlComparator(
|
||||
test_cli20.end_url(path, query, format=self.format),
|
||||
self.client),
|
||||
'GET', body=None,
|
||||
headers=mox.ContainsKeyValue(
|
||||
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(
|
||||
(test_cli20.MyResp(200), resstr))
|
||||
self.mox.ReplayAll()
|
||||
cmd_parser = cmd.get_parser("list_networks")
|
||||
shell.run_command(cmd, cmd_parser, args)
|
||||
self.mox.VerifyAll()
|
||||
self.mox.UnsetStubs()
|
||||
_str = self.fake_stdout.make_string()
|
||||
self.assertIn('myid', _str)
|
||||
|
||||
def _make_tag_path(self, resource, resource_id, tag):
|
||||
path = getattr(self.client, "tag_path")
|
||||
resource_plural = neutronV2_0._get_resource_plural(resource,
|
||||
self.client)
|
||||
return path % (resource_plural, resource_id, tag)
|
||||
|
||||
def _make_tags_path(self, resource, resource_id):
|
||||
path = getattr(self.client, "tags_path")
|
||||
resource_plural = neutronV2_0._get_resource_plural(resource,
|
||||
self.client)
|
||||
return path % (resource_plural, resource_id)
|
||||
|
||||
def test_add_tag(self):
|
||||
cmd = tag.AddTag(test_cli20.MyApp(sys.stdout), None)
|
||||
path = self._make_tag_path('network', 'myid', 'red')
|
||||
args = ['--resource-type', 'network', '--resource', 'myid',
|
||||
'--tag', 'red']
|
||||
self._test_tag_operation(cmd, path, 'PUT', args, "tag-add")
|
||||
|
||||
def test_replace_tag(self):
|
||||
cmd = tag.ReplaceTag(test_cli20.MyApp(sys.stdout), None)
|
||||
path = self._make_tags_path('network', 'myid')
|
||||
args = ['--resource-type', 'network', '--resource', 'myid',
|
||||
'--tag', 'red', '--tag', 'blue']
|
||||
body = {'tags': ['red', 'blue']}
|
||||
self._test_tag_operation(cmd, path, 'PUT', args, "tag-replace",
|
||||
body=body)
|
||||
|
||||
def test_remove_tag(self):
|
||||
cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None)
|
||||
path = self._make_tag_path('network', 'myid', 'red')
|
||||
args = ['--resource-type', 'network', '--resource', 'myid',
|
||||
'--tag', 'red']
|
||||
self._test_tag_operation(cmd, path, 'DELETE', args, "tag-remove")
|
||||
|
||||
def test_remove_tag_all(self):
|
||||
cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None)
|
||||
path = self._make_tags_path('network', 'myid')
|
||||
args = ['--resource-type', 'network', '--resource', 'myid',
|
||||
'--all']
|
||||
self._test_tag_operation(cmd, path, 'DELETE', args, "tag-remove")
|
||||
|
||||
def test_no_tag_nor_all(self):
|
||||
cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None)
|
||||
path = self._make_tags_path('network', 'myid')
|
||||
args = ['--resource-type', 'network', '--resource', 'myid']
|
||||
self.assertRaises(exceptions.CommandError, self._test_tag_operation,
|
||||
cmd, path, 'DELETE', args, "tag-remove")
|
||||
|
||||
def test_tags_query(self):
|
||||
# This test examines that '-' in the tag related filters
|
||||
# is not converted to '_'.
|
||||
resources = 'networks'
|
||||
cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None)
|
||||
self.mox.StubOutWithMock(network.ListNetwork, "extend_list")
|
||||
network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg())
|
||||
args = ['--not-tags', 'red,blue', '--tags-any', 'green',
|
||||
'--not-tags-any', 'black']
|
||||
query = "not-tags=red,blue&tags-any=green¬-tags-any=black"
|
||||
self._test_tags_query(cmd, resources, args, query)
|
|
@ -528,6 +528,8 @@ class Client(ClientBase):
|
|||
bgp_peer_path = "/bgp-peers/%s"
|
||||
network_ip_availabilities_path = '/network-ip-availabilities'
|
||||
network_ip_availability_path = '/network-ip-availabilities/%s'
|
||||
tags_path = "/%s/%s/tags"
|
||||
tag_path = "/%s/%s/tags/%s"
|
||||
|
||||
# API has no way to report plurals, so we have to hard code them
|
||||
EXTED_PLURALS = {'routers': 'router',
|
||||
|
@ -2002,6 +2004,26 @@ class Client(ClientBase):
|
|||
return self.get(self.network_ip_availability_path % (network),
|
||||
params=_params)
|
||||
|
||||
@APIParamsCall
|
||||
def add_tag(self, resource_type, resource_id, tag, **_params):
|
||||
"""Add a tag on the resource."""
|
||||
return self.put(self.tag_path % (resource_type, resource_id, tag))
|
||||
|
||||
@APIParamsCall
|
||||
def replace_tag(self, resource_type, resource_id, body, **_params):
|
||||
"""Replace tags on the resource."""
|
||||
return self.put(self.tags_path % (resource_type, resource_id), body)
|
||||
|
||||
@APIParamsCall
|
||||
def remove_tag(self, resource_type, resource_id, tag, **_params):
|
||||
"""Remove a tag on the resource."""
|
||||
return self.delete(self.tag_path % (resource_type, resource_id, tag))
|
||||
|
||||
@APIParamsCall
|
||||
def remove_tag_all(self, resource_type, resource_id, **_params):
|
||||
"""Remove all tags on the resource."""
|
||||
return self.delete(self.tags_path % (resource_type, resource_id))
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize a new client for the Neutron v2.0 API."""
|
||||
super(Client, self).__init__(**kwargs)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
CLI support for tag.
|
||||
|
||||
* The ``tag-add`` command sets a tag on the network resource. It also
|
||||
includes ``--resource-type``, ``--resource`` and ``--tag`` options.
|
||||
* The ``tag-replace`` command replaces tags on the network resource. It
|
||||
also includes ``--resource-type``, ``--resource`` and ``--tag``
|
||||
options. More than one ``--tag`` options can be set.
|
||||
* The ``tag-remove`` command removes tags on the network resource. It also
|
||||
includes ``--resource-type``, ``--resource``, ``--tag`` and ``--all``
|
||||
options. The ``--all`` option allow to remove all tags on the network
|
||||
resource.
|
||||
* The ``net-list`` command includes ``--tags``, ``--tags-any``,
|
||||
``--not-tags`` and ``--not-tags-any`` options.
|
Loading…
Reference in New Issue