Merge "Bulk creation of SecurityGroups"
diff --git a/neutron/tests/tempest/api/base.py b/neutron/tests/tempest/api/base.py
index b308a31..0d77064 100644
--- a/neutron/tests/tempest/api/base.py
+++ b/neutron/tests/tempest/api/base.py
@@ -109,6 +109,7 @@
cls.admin_address_scopes = []
cls.subnetpools = []
cls.admin_subnetpools = []
+ cls.security_groups = []
@classmethod
def resource_cleanup(cls):
@@ -167,6 +168,11 @@
cls._try_delete_resource(cls.admin_client.delete_network,
network['id'])
+ # Clean up security groups
+ for secgroup in cls.security_groups:
+ cls._try_delete_resource(cls.client.delete_security_group,
+ secgroup['id'])
+
for subnetpool in cls.subnetpools:
cls._try_delete_resource(cls.client.delete_subnetpool,
subnetpool['id'])
@@ -343,6 +349,11 @@
return interface
@classmethod
+ def get_supported_qos_rule_types(cls):
+ body = cls.client.list_qos_rule_types()
+ return [rule_type['type'] for rule_type in body['rule_types']]
+
+ @classmethod
def create_qos_policy(cls, name, description=None, shared=False,
tenant_id=None):
"""Wrapper utility that returns a test QoS policy."""
@@ -486,6 +497,18 @@
raise exceptions.InvalidConfiguration(message)
+def require_qos_rule_type(rule_type):
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ if rule_type not in self.get_supported_qos_rule_types():
+ raise self.skipException(
+ "%s rule type is required." % rule_type)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
def _require_sorting(f):
@functools.wraps(f)
def inner(self, *args, **kwargs):
diff --git a/neutron/tests/tempest/api/test_floating_ips.py b/neutron/tests/tempest/api/test_floating_ips.py
index bafa54c..6e722db 100644
--- a/neutron/tests/tempest/api/test_floating_ips.py
+++ b/neutron/tests/tempest/api/test_floating_ips.py
@@ -100,4 +100,4 @@
# disassociate
body = self.client.update_floatingip(body['floatingip']['id'],
port_id=None)
- self.assertEqual(None, body['floatingip']['port_id'])
+ self.assertIsNone(body['floatingip']['port_id'])
diff --git a/neutron/tests/tempest/api/test_metering_extensions.py b/neutron/tests/tempest/api/test_metering_extensions.py
index 7b03386..9bbcdce 100644
--- a/neutron/tests/tempest/api/test_metering_extensions.py
+++ b/neutron/tests/tempest/api/test_metering_extensions.py
@@ -12,13 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+from neutron_lib.db import constants as db_const
from tempest.lib.common.utils import data_utils
from tempest import test
-from neutron.api.v2 import attributes as attr
from neutron.tests.tempest.api import base
-LONG_NAME_OK = 'x' * (attr.NAME_MAX_LEN)
+LONG_NAME_OK = 'x' * db_const.NAME_FIELD_SIZE
class MeteringTestJSON(base.BaseAdminNetworkTest):
diff --git a/neutron/tests/tempest/api/test_metering_negative.py b/neutron/tests/tempest/api/test_metering_negative.py
index 39fdae8..dece9e4 100644
--- a/neutron/tests/tempest/api/test_metering_negative.py
+++ b/neutron/tests/tempest/api/test_metering_negative.py
@@ -12,13 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+from neutron_lib.db import constants as db_const
from tempest.lib import exceptions as lib_exc
from tempest import test
-from neutron.api.v2 import attributes as attr
from neutron.tests.tempest.api import base
-LONG_NAME_NG = 'x' * (attr.NAME_MAX_LEN + 1)
+LONG_NAME_NG = 'x' * (db_const.NAME_FIELD_SIZE + 1)
class MeteringNegativeTestJSON(base.BaseAdminNetworkTest):
diff --git a/neutron/tests/tempest/api/test_qos.py b/neutron/tests/tempest/api/test_qos.py
index 2f1c75a..395bda0 100644
--- a/neutron/tests/tempest/api/test_qos.py
+++ b/neutron/tests/tempest/api/test_qos.py
@@ -361,6 +361,7 @@
class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
@classmethod
@test.requires_ext(extension="qos", service="network")
+ @base.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
def resource_setup(cls):
super(QosBandwidthLimitRuleTestJSON, cls).resource_setup()
@@ -772,6 +773,7 @@
@classmethod
@test.requires_ext(extension="qos", service="network")
+ @base.require_qos_rule_type(qos_consts.RULE_TYPE_DSCP_MARKING)
def resource_setup(cls):
super(QosDscpMarkingRuleTestJSON, cls).resource_setup()
@@ -905,6 +907,7 @@
@classmethod
@test.requires_ext(extension="qos", service="network")
+ @base.require_qos_rule_type(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH)
def resource_setup(cls):
super(QosMinimumBandwidthRuleTestJSON, cls).resource_setup()
diff --git a/neutron/tests/tempest/api/test_qos_negative.py b/neutron/tests/tempest/api/test_qos_negative.py
new file mode 100644
index 0000000..5057c72
--- /dev/null
+++ b/neutron/tests/tempest/api/test_qos_negative.py
@@ -0,0 +1,50 @@
+# 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 neutron_lib.db import constants as db_const
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron.tests.tempest.api import base
+
+LONG_NAME_NG = 'z' * (db_const.NAME_FIELD_SIZE + 1)
+LONG_DESCRIPTION_NG = 'z' * (db_const.LONG_DESCRIPTION_FIELD_SIZE + 1)
+LONG_TENANT_ID_NG = 'z' * (db_const.PROJECT_ID_FIELD_SIZE + 1)
+
+
+class QosNegativeTestJSON(base.BaseAdminNetworkTest):
+ @classmethod
+ @test.requires_ext(extension="qos", service="network")
+ def resource_setup(cls):
+ super(QosNegativeTestJSON, cls).resource_setup()
+
+ @test.attr(type='negative')
+ @test.idempotent_id('b9dce555-d3b3-11e5-950a-54ee757c77da')
+ def test_add_policy_with_too_long_name(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_qos_policy,
+ LONG_NAME_NG, 'test policy desc1', False)
+
+ @test.attr(type='negative')
+ @test.idempotent_id('b9dce444-d3b3-11e5-950a-54ee747c99db')
+ def test_add_policy_with_too_long_description(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_qos_policy,
+ 'test-policy', LONG_DESCRIPTION_NG, False)
+
+ @test.attr(type='negative')
+ @test.idempotent_id('b9dce444-d3b3-11e5-950a-54ee757c77dc')
+ def test_add_policy_with_too_long_tenant_id(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_qos_policy,
+ 'test-policy', 'test policy desc1',
+ False, LONG_TENANT_ID_NG)
diff --git a/neutron/tests/tempest/api/test_revisions.py b/neutron/tests/tempest/api/test_revisions.py
index 6a2ff88..cdcb367 100644
--- a/neutron/tests/tempest/api/test_revisions.py
+++ b/neutron/tests/tempest/api/test_revisions.py
@@ -261,3 +261,54 @@
body['revision_number'])
# disassociate
self.client.update_floatingip(b2['floatingip']['id'], port_id=None)
+
+ @test.idempotent_id('afb6486c-41b5-483e-a500-3c506f4deb49')
+ @test.requires_ext(extension="router", service="network")
+ @test.requires_ext(extension="dvr", service="network")
+ def test_update_router_extra_attributes_bumps_revision(self):
+ router = self.create_router(router_name='r1')
+ self.assertIn('revision_number', router)
+ rev1 = router['revision_number']
+ router = self.admin_client.update_router(
+ router['id'], admin_state_up=False)['router']
+ self.assertGreater(router['revision_number'], rev1)
+ self.admin_client.update_router(router['id'],
+ distributed=True)['router']
+ updated = self.client.show_router(router['id'])['router']
+ self.assertGreater(updated['revision_number'],
+ router['revision_number'])
+
+ @test.idempotent_id('90743b00-b0e2-40e4-9524-1c884fe3ef23')
+ @test.requires_ext(extension="external-network", service="network")
+ @test.requires_ext(extension="auto-allocated-topology", service="network")
+ @test.requires_ext(extension="subnet_allocation", service="network")
+ @test.requires_ext(extension="router", service="network")
+ def test_update_external_network_bumps_revision(self):
+ net = self.create_network()
+ self.assertIn('revision_number', net)
+ updated = self.admin_client.update_network(net['id'],
+ **{'router:external': True})
+ self.assertGreater(updated['network']['revision_number'],
+ net['revision_number'])
+
+ @test.idempotent_id('5af6450a-0f61-49c3-b628-38db77c7b856')
+ @test.requires_ext(extension="qos", service="network")
+ def test_update_qos_port_policy_binding_bumps_revision(self):
+ policy = self.create_qos_policy(name='port-policy', shared=False)
+ port = self.create_port(self.create_network())
+ self.addCleanup(self.client.delete_port, port['id'])
+ updated = self.admin_client.update_port(
+ port['id'], qos_policy_id=policy['id'])
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+
+ @test.idempotent_id('817da343-c6e4-445c-9519-a621f124dfbe')
+ @test.requires_ext(extension="qos", service="network")
+ def test_update_qos_network_policy_binding_bumps_revision(self):
+ policy = self.create_qos_policy(name='network-policy', shared=False)
+ network = self.create_network()
+ self.addCleanup(self.client.delete_network, network['id'])
+ updated = self.admin_client.update_network(
+ network['id'], qos_policy_id=policy['id'])
+ self.assertGreater(updated['network']['revision_number'],
+ network['revision_number'])
diff --git a/neutron/tests/tempest/api/test_tag.py b/neutron/tests/tempest/api/test_tag.py
new file mode 100644
index 0000000..5cf6e23
--- /dev/null
+++ b/neutron/tests/tempest/api/test_tag.py
@@ -0,0 +1,174 @@
+# 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 tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron.tests.tempest.api import base
+
+
+class TagTestJSON(base.BaseAdminNetworkTest):
+
+ @classmethod
+ @test.requires_ext(extension="tag", service="network")
+ def resource_setup(cls):
+ super(TagTestJSON, cls).resource_setup()
+ cls.res_id = cls._create_resource()
+
+ def _get_and_compare_tags(self, tags):
+ res_body = self.client.get_tags(self.resource, self.res_id)
+ self.assertItemsEqual(tags, res_body['tags'])
+
+ def _test_tag_operations(self):
+ # create and get tags
+ tags = ['red', 'blue']
+ res_body = self.client.update_tags(self.resource, self.res_id, tags)
+ self.assertItemsEqual(tags, res_body['tags'])
+ self._get_and_compare_tags(tags)
+
+ # add a tag
+ self.client.update_tag(self.resource, self.res_id, 'green')
+ self._get_and_compare_tags(['red', 'blue', 'green'])
+
+ # update tag exist
+ self.client.update_tag(self.resource, self.res_id, 'red')
+ self._get_and_compare_tags(['red', 'blue', 'green'])
+
+ # replace tags
+ tags = ['red', 'yellow', 'purple']
+ res_body = self.client.update_tags(self.resource, self.res_id, tags)
+ self.assertItemsEqual(tags, res_body['tags'])
+ self._get_and_compare_tags(tags)
+
+ # get tag
+ self.client.get_tag(self.resource, self.res_id, 'red')
+
+ # get tag not exist
+ self.assertRaises(lib_exc.NotFound, self.client.get_tag,
+ self.resource, self.res_id, 'green')
+
+ # delete tag
+ self.client.delete_tag(self.resource, self.res_id, 'red')
+ self._get_and_compare_tags(['yellow', 'purple'])
+
+ # delete tag not exist
+ self.assertRaises(lib_exc.NotFound, self.client.delete_tag,
+ self.resource, self.res_id, 'green')
+
+ # delete tags
+ self.client.delete_tags(self.resource, self.res_id)
+ self._get_and_compare_tags([])
+
+
+class TagNetworkTestJSON(TagTestJSON):
+ resource = 'networks'
+
+ @classmethod
+ def _create_resource(cls):
+ network = cls.create_network()
+ return network['id']
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('5621062d-fbfb-4437-9d69-138c78ea4188')
+ def test_network_tags(self):
+ self._test_tag_operations()
+
+
+class TagFilterTestJSON(base.BaseAdminNetworkTest):
+ credentials = ['primary', 'alt', 'admin']
+ resource = 'networks'
+
+ @classmethod
+ @test.requires_ext(extension="tag", service="network")
+ def resource_setup(cls):
+ super(TagFilterTestJSON, cls).resource_setup()
+
+ res1_id = cls._create_resource('tag-res1')
+ res2_id = cls._create_resource('tag-res2')
+ res3_id = cls._create_resource('tag-res3')
+ res4_id = cls._create_resource('tag-res4')
+ # tag-res5: a resource without tags
+ cls._create_resource('tag-res5')
+
+ cls.client.update_tags(cls.resource, res1_id, ['red'])
+ cls.client.update_tags(cls.resource, res2_id, ['red', 'blue'])
+ cls.client.update_tags(cls.resource, res3_id,
+ ['red', 'blue', 'green'])
+ cls.client.update_tags(cls.resource, res4_id, ['green'])
+
+ @classmethod
+ def setup_clients(cls):
+ super(TagFilterTestJSON, cls).setup_clients()
+ cls.client = cls.alt_manager.network_client
+
+ def _assertEqualResources(self, expected, res):
+ actual = [n['name'] for n in res if n['name'].startswith('tag-res')]
+ self.assertEqual(set(expected), set(actual))
+
+ def _test_filter_tags(self):
+ # tags single
+ filters = {'tags': 'red'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res2', 'tag-res3'], res)
+
+ # tags multi
+ filters = {'tags': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res2', 'tag-res3'], res)
+
+ # tags-any single
+ filters = {'tags-any': 'blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res2', 'tag-res3'], res)
+
+ # tags-any multi
+ filters = {'tags-any': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res2', 'tag-res3'], res)
+
+ # not-tags single
+ filters = {'not-tags': 'red'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res4', 'tag-res5'], res)
+
+ # not-tags multi
+ filters = {'not-tags': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res4', 'tag-res5'], res)
+
+ # not-tags-any single
+ filters = {'not-tags-any': 'blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res4', 'tag-res5'], res)
+
+ # not-tags-any multi
+ filters = {'not-tags-any': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res4', 'tag-res5'], res)
+
+
+class TagFilterNetworkTestJSON(TagFilterTestJSON):
+ resource = 'networks'
+
+ @classmethod
+ def _create_resource(cls, name):
+ res = cls.create_network(network_name=name)
+ return res['id']
+
+ def _list_resource(self, filters):
+ res = self.client.list_networks(**filters)
+ return res['networks']
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('a66b5cca-7db2-40f5-a33d-8ac9f864e53e')
+ def test_filter_network_tags(self):
+ self._test_filter_tags()
diff --git a/neutron/tests/tempest/scenario/base.py b/neutron/tests/tempest/scenario/base.py
index 8c45f4b..56221e5 100644
--- a/neutron/tests/tempest/scenario/base.py
+++ b/neutron/tests/tempest/scenario/base.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import log
+
from tempest.common import waiters
from tempest.lib.common import ssh
from tempest.lib.common.utils import data_utils
@@ -23,6 +25,8 @@
CONF = config.CONF
+LOG = log.getLogger(__name__)
+
class BaseTempestTestCase(base_api.BaseNetworkTest):
@classmethod
@@ -47,13 +51,37 @@
@classmethod
def create_server(cls, flavor_ref, image_ref, key_name, networks,
- name=None):
+ name=None, security_groups=None):
+ """Create a server using tempest lib
+ All the parameters are the ones used in Compute API
+
+ Args:
+ flavor_ref(str): The flavor of the server to be provisioned.
+ image_ref(str): The image of the server to be provisioned.
+ key_name(str): SSH key to to be used to connect to the
+ provisioned server.
+ networks(list): List of dictionaries where each represent
+ an interface to be attached to the server. For network
+ it should be {'uuid': network_uuid} and for port it should
+ be {'port': port_uuid}
+ name(str): Name of the server to be provisioned.
+ security_groups(list): List of dictionaries where
+ the keys is 'name' and the value is the name of
+ the security group. If it's not passed the default
+ security group will be used.
+ """
+
name = name or data_utils.rand_name('server-test')
+ if not security_groups:
+ security_groups = [{'name': 'default'}]
+
server = cls.manager.servers_client.create_server(
- name=name, flavorRef=flavor_ref,
+ name=name,
+ flavorRef=flavor_ref,
imageRef=image_ref,
key_name=key_name,
- networks=networks)
+ networks=networks,
+ security_groups=security_groups)
cls.servers.append(server['server']['id'])
return server
@@ -104,6 +132,7 @@
router = cls.create_router(
data_utils.rand_name('router'), admin_state_up=True,
external_network_id=CONF.network.public_network_id)
+ LOG.debug("Created router %s", router['name'])
cls.create_router_interface(router['id'], subnet_id)
cls.routers.append(router)
return router
@@ -123,17 +152,32 @@
@classmethod
def setup_network_and_server(cls):
+ """Creating network resources and a server.
+
+ Creating a network, subnet, router, keypair, security group
+ and a server.
+ """
cls.network = cls.create_network()
+ LOG.debug("Created network %s", cls.network['name'])
cls.subnet = cls.create_subnet(cls.network)
+ LOG.debug("Created subnet %s", cls.subnet['id'])
+
+ secgroup = cls.manager.network_client.create_security_group(
+ name=data_utils.rand_name('secgroup-'))
+ LOG.debug("Created security group %s",
+ secgroup['security_group']['name'])
+ cls.security_groups.append(secgroup['security_group'])
cls.create_router_and_interface(cls.subnet['id'])
cls.keypair = cls.create_keypair()
- cls.create_loginable_secgroup_rule()
+ cls.create_loginable_secgroup_rule(
+ secgroup_id=secgroup['security_group']['id'])
cls.server = cls.create_server(
flavor_ref=CONF.compute.flavor_ref,
image_ref=CONF.compute.image_ref,
key_name=cls.keypair['name'],
- networks=[{'uuid': cls.network['id']}])
+ networks=[{'uuid': cls.network['id']}],
+ security_groups=[{'name': secgroup['security_group']['name']}])
waiters.wait_for_server_status(cls.manager.servers_client,
cls.server['server']['id'],
constants.SERVER_STATUS_ACTIVE)
diff --git a/neutron/tests/tempest/scenario/test_qos.py b/neutron/tests/tempest/scenario/test_qos.py
index 89b31a7..b558438 100644
--- a/neutron/tests/tempest/scenario/test_qos.py
+++ b/neutron/tests/tempest/scenario/test_qos.py
@@ -22,6 +22,8 @@
from tempest import test
from neutron.common import utils
+from neutron.services.qos import qos_consts
+from neutron.tests.tempest.api import base as base_api
from neutron.tests.tempest import config
from neutron.tests.tempest.scenario import base
from neutron.tests.tempest.scenario import constants
@@ -79,6 +81,7 @@
@classmethod
@test.requires_ext(extension="qos", service="network")
+ @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
def resource_setup(cls):
super(QoSTest, cls).resource_setup()
@@ -158,7 +161,9 @@
'port_range_min': NC_PORT,
'port_range_max': NC_PORT,
'remote_ip_prefix': '0.0.0.0/0'}]
- self.create_secgroup_rules(rulesets)
+ self.create_secgroup_rules(rulesets,
+ self.security_groups[-1]['id'])
+
ssh_client = ssh.Client(self.fip['floating_ip_address'],
CONF.validation.image_ssh_user,
pkey=self.keypair['private_key'])
diff --git a/neutron/tests/tempest/scenario/test_trunk.py b/neutron/tests/tempest/scenario/test_trunk.py
index 30d6022..b350392 100644
--- a/neutron/tests/tempest/scenario/test_trunk.py
+++ b/neutron/tests/tempest/scenario/test_trunk.py
@@ -14,6 +14,7 @@
from oslo_log import log as logging
from tempest.common import waiters
+from tempest.lib.common.utils import data_utils
from tempest import test
from neutron.common import utils
@@ -38,17 +39,24 @@
cls.subnet = cls.create_subnet(cls.network)
cls.create_router_and_interface(cls.subnet['id'])
cls.keypair = cls.create_keypair()
- cls.create_loginable_secgroup_rule()
+ cls.secgroup = cls.manager.network_client.create_security_group(
+ name=data_utils.rand_name('secgroup-'))
+ cls.security_groups.append(cls.secgroup['security_group'])
+ cls.create_loginable_secgroup_rule(
+ secgroup_id=cls.secgroup['security_group']['id'])
def _create_server_with_trunk_port(self):
- port = self.create_port(self.network)
+ port = self.create_port(self.network, security_groups=[
+ self.secgroup['security_group']['id']])
trunk = self.client.create_trunk(port['id'], subports=[])['trunk']
fip = self.create_and_associate_floatingip(port['id'])
server = self.create_server(
flavor_ref=CONF.compute.flavor_ref,
image_ref=CONF.compute.image_ref,
key_name=self.keypair['name'],
- networks=[{'port': port['id']}])['server']
+ networks=[{'port': port['id']}],
+ security_groups=[{'name': self.secgroup[
+ 'security_group']['name']}])['server']
self.addCleanup(self._detach_and_delete_trunk, server, trunk)
return {'port': port, 'trunk': trunk, 'fip': fip,
'server': server}
diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py
index 93e31ea..610b049 100644
--- a/neutron/tests/tempest/services/network/json/network_client.py
+++ b/neutron/tests/tempest/services/network/json/network_client.py
@@ -907,3 +907,44 @@
body = {'extensions': self.deserialize_list(body)}
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
+
+ def get_tags(self, resource_type, resource_id):
+ uri = '%s/%s/%s/tags' % (
+ self.uri_prefix, resource_type, resource_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = jsonutils.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def get_tag(self, resource_type, resource_id, tag):
+ uri = '%s/%s/%s/tags/%s' % (
+ self.uri_prefix, resource_type, resource_id, tag)
+ resp, body = self.get(uri)
+ self.expected_success(204, resp.status)
+
+ def update_tag(self, resource_type, resource_id, tag):
+ uri = '%s/%s/%s/tags/%s' % (
+ self.uri_prefix, resource_type, resource_id, tag)
+ resp, body = self.put(uri, None)
+ self.expected_success(201, resp.status)
+
+ def update_tags(self, resource_type, resource_id, tags):
+ uri = '%s/%s/%s/tags' % (
+ self.uri_prefix, resource_type, resource_id)
+ req_body = jsonutils.dumps({'tags': tags})
+ resp, body = self.put(uri, req_body)
+ self.expected_success(200, resp.status)
+ body = jsonutils.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def delete_tags(self, resource_type, resource_id):
+ uri = '%s/%s/%s/tags' % (
+ self.uri_prefix, resource_type, resource_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+
+ def delete_tag(self, resource_type, resource_id, tag):
+ uri = '%s/%s/%s/tags/%s' % (
+ self.uri_prefix, resource_type, resource_id, tag)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)