Merge "Use correct routers_client in _delete_router"
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index e7f71bc..572d425 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -246,4 +246,4 @@
* `2.42`_
- .. _2.42: http://docs.openstack.org/developer/nova/api_microversion_history.html#id38
+ .. _2.42: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-ocata
diff --git a/releasenotes/notes/add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml b/releasenotes/notes/add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml
new file mode 100644
index 0000000..9116ef8
--- /dev/null
+++ b/releasenotes/notes/add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add the implied roles feature API to the roles_client library. This
+ feature enables the possibility to create inferences rules between
+ roles (a role being implied by another role).
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 85c7fba..9e1c0e9 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -18,6 +18,7 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest.lib import decorators
+from tempest import test
class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -91,6 +92,7 @@
self.assertIn(self.s1_name, servers_name)
self.assertIn(self.s2_name, servers_name)
+ @test.related_bug('1659811')
@decorators.idempotent_id('7e5d6b8f-454a-4ba1-8ae2-da857af8338b')
def test_list_servers_by_admin_with_specified_tenant(self):
# In nova v2, tenant_id is ignored unless all_tenants is specified
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index e6c065f..c3c5460 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -304,8 +304,26 @@
cls.images.append(image_id)
if 'wait_until' in kwargs:
- waiters.wait_for_image_status(cls.compute_images_client,
- image_id, kwargs['wait_until'])
+ try:
+ waiters.wait_for_image_status(cls.compute_images_client,
+ image_id, kwargs['wait_until'])
+ except lib_exc.NotFound:
+ if kwargs['wait_until'].upper() == 'ACTIVE':
+ # If the image is not found after create_image returned
+ # that means the snapshot failed in nova-compute and nova
+ # deleted the image. There should be a compute fault
+ # recorded with the server in that case, so get the server
+ # and dump some details.
+ server = (
+ cls.servers_client.show_server(server_id)['server'])
+ if 'fault' in server:
+ raise exceptions.SnapshotNotFoundException(
+ server['fault'], image_id=image_id)
+ else:
+ raise exceptions.SnapshotNotFoundException(
+ image_id=image_id)
+ else:
+ raise
image = cls.compute_images_client.show_image(image_id)['image']
return image
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 6e6122b..7938604 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -90,8 +90,11 @@
vendor = CONF.volume.vendor_name
extra_specs = {"storage_protocol": proto,
"vendor_name": vendor}
- body = self.create_volume_type(description=description, name=name,
- extra_specs=extra_specs)
+ params = {'name': name,
+ 'description': description,
+ 'extra_specs': extra_specs,
+ 'os-volume-type-access:is_public': True}
+ body = self.create_volume_type(**params)
self.assertIn('name', body)
self.assertEqual(name, body['name'],
"The created volume_type name is not equal "
@@ -112,6 +115,12 @@
self.assertEqual(extra_specs, fetched_volume_type['extra_specs'],
'The fetched Volume_type is different '
'from the created Volume_type')
+ self.assertEqual(description, fetched_volume_type['description'])
+ self.assertEqual(body['is_public'],
+ fetched_volume_type['is_public'])
+ self.assertEqual(
+ body['os-volume-type-access:is_public'],
+ fetched_volume_type['os-volume-type-access:is_public'])
@decorators.idempotent_id('7830abd0-ff99-4793-a265-405684a54d46')
def test_volume_type_encryption_create_get_delete(self):
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 43f919a..45bbc11 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -25,6 +25,10 @@
message = "Server %(server_id)s failed to build and is in ERROR status"
+class SnapshotNotFoundException(exceptions.TempestException):
+ message = "Server snapshot image %(image_id)s not found."
+
+
class ImageKilledException(exceptions.TempestException):
message = "Image %(image_id)s 'killed' while waiting for '%(status)s'"
diff --git a/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
index f7b77a1..0dc28c3 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
@@ -51,7 +51,7 @@
}
detail = {
- 'type': 'object',
+ 'type': ['object', 'null'],
'patternProperties': {
# NOTE: Here is for a hostname
'^[a-zA-Z0-9-_.]+$': {
diff --git a/tempest/lib/services/identity/v3/roles_client.py b/tempest/lib/services/identity/v3/roles_client.py
index f1339dd..0df23ce 100644
--- a/tempest/lib/services/identity/v3/roles_client.py
+++ b/tempest/lib/services/identity/v3/roles_client.py
@@ -190,3 +190,40 @@
(domain_id, group_id, role_id))
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp)
+
+ def create_role_inference_rule(self, prior_role, implies_role):
+ """Create a role inference rule."""
+ resp, body = self.put('roles/%s/implies/%s' %
+ (prior_role, implies_role), None)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_role_inference_rule(self, prior_role, implies_role):
+ """Get a role inference rule."""
+ resp, body = self.get('roles/%s/implies/%s' %
+ (prior_role, implies_role))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_role_inferences_rules(self, prior_role):
+ """List the inferences rules from a role."""
+ resp, body = self.get('roles/%s/implies' % prior_role)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_role_inference_rule(self, prior_role, implies_role):
+ """Check a role inference rule."""
+ resp, body = self.head('roles/%s/implies/%s' %
+ (prior_role, implies_role))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def delete_role_inference_rule(self, prior_role, implies_role):
+ """Delete a role inference rule."""
+ resp, body = self.delete('roles/%s/implies/%s' %
+ (prior_role, implies_role))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/tests/api/compute/test_base.py b/tempest/tests/api/compute/test_base.py
index 00a9a37..a1da343 100644
--- a/tempest/tests/api/compute/test_base.py
+++ b/tempest/tests/api/compute/test_base.py
@@ -15,9 +15,12 @@
import mock
from oslo_utils import uuidutils
+import six
from tempest.api.compute import base as compute_base
from tempest.common import waiters
+from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
@@ -65,3 +68,73 @@
wait_for_image_status.assert_called_once_with(
compute_images_client, image_id, 'ACTIVE')
compute_images_client.show_image.assert_called_once_with(image_id)
+
+ @mock.patch.multiple(compute_base.BaseV2ComputeTest,
+ compute_images_client=mock.DEFAULT,
+ servers_client=mock.DEFAULT,
+ images=[], create=True)
+ @mock.patch.object(waiters, 'wait_for_image_status',
+ side_effect=lib_exc.NotFound)
+ def _test_create_image_from_server_wait_until_active_not_found(
+ self, wait_for_image_status, compute_images_client,
+ servers_client, fault=None):
+ # setup mocks
+ image_id = uuidutils.generate_uuid()
+ fake_image = mock.Mock(response={'location': image_id})
+ compute_images_client.create_image.return_value = fake_image
+ fake_server = {'id': mock.sentinel.server_id}
+ if fault:
+ fake_server['fault'] = fault
+ servers_client.show_server.return_value = {'server': fake_server}
+ # call the utility method
+ ex = self.assertRaises(
+ exceptions.SnapshotNotFoundException,
+ compute_base.BaseV2ComputeTest.create_image_from_server,
+ mock.sentinel.server_id, wait_until='active')
+ # make our assertions
+ if fault:
+ self.assertIn(fault, six.text_type(ex))
+ else:
+ self.assertNotIn(fault, six.text_type(ex))
+ wait_for_image_status.assert_called_once_with(
+ compute_images_client, image_id, 'active')
+ servers_client.show_server.assert_called_once_with(
+ mock.sentinel.server_id)
+
+ def test_create_image_from_server_wait_until_active_not_found_no_fault(
+ self):
+ # Tests create_image_from_server with wait_until='active' kwarg and
+ # the a 404 is raised while waiting for the image status to change. In
+ # this test the server does not have a fault associated with it.
+ self._test_create_image_from_server_wait_until_active_not_found()
+
+ def test_create_image_from_server_wait_until_active_not_found_with_fault(
+ self):
+ # Tests create_image_from_server with wait_until='active' kwarg and
+ # the a 404 is raised while waiting for the image status to change. In
+ # this test the server has a fault associated with it.
+ self._test_create_image_from_server_wait_until_active_not_found(
+ fault='Lost connection to hypervisor!')
+
+ @mock.patch.multiple(compute_base.BaseV2ComputeTest,
+ compute_images_client=mock.DEFAULT,
+ images=[], create=True)
+ @mock.patch.object(waiters, 'wait_for_image_status',
+ side_effect=lib_exc.NotFound)
+ def test_create_image_from_server_wait_until_saving_not_found(
+ self, wait_for_image_status, compute_images_client):
+ # Tests create_image_from_server with wait_until='SAVING' kwarg and
+ # the a 404 is raised while waiting for the image status to change. In
+ # this case we do not get the server details and just re-raise the 404.
+ # setup mocks
+ image_id = uuidutils.generate_uuid()
+ fake_image = mock.Mock(response={'location': image_id})
+ compute_images_client.create_image.return_value = fake_image
+ # call the utility method
+ self.assertRaises(
+ lib_exc.NotFound,
+ compute_base.BaseV2ComputeTest.create_image_from_server,
+ mock.sentinel.server_id, wait_until='SAVING')
+ # make our assertions
+ wait_for_image_status.assert_called_once_with(
+ compute_images_client, image_id, 'SAVING')
diff --git a/tempest/tests/lib/services/identity/v3/test_roles_client.py b/tempest/tests/lib/services/identity/v3/test_roles_client.py
index 4f70b47..41cea85 100644
--- a/tempest/tests/lib/services/identity/v3/test_roles_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_roles_client.py
@@ -52,6 +52,65 @@
FAKE_LIST_ROLES = {"roles": [FAKE_ROLE_INFO, FAKE_ROLE_INFO_2]}
+ FAKE_ROLE_INFERENCE_RULE = {
+ "role_inference": {
+ "prior_role": {
+ "id": FAKE_ROLE_ID,
+ "name": FAKE_ROLE_NAME,
+ "links": {
+ "self": "http://example.com/identity/v3/roles/%s" % (
+ FAKE_ROLE_ID)
+ }
+ },
+ "implies": {
+ "id": FAKE_ROLE_ID_2,
+ "name": FAKE_ROLE_NAME_2,
+ "links": {
+ "self": "http://example.com/identity/v3/roles/%s" % (
+ FAKE_ROLE_ID_2)
+ }
+ }
+ },
+ "links": {
+ "self": "http://example.com/identity/v3/roles/"
+ "%s/implies/%s" % (FAKE_ROLE_ID, FAKE_ROLE_ID_2)
+ }
+ }
+
+ FAKE_LIST_ROLE_INFERENCES_RULES = {
+ "role_inference": {
+ "prior_role": {
+ "id": FAKE_ROLE_ID,
+ "name": FAKE_ROLE_NAME,
+ "links": {
+ "self": "http://example.com/identity/v3/roles/%s" % (
+ FAKE_ROLE_ID)
+ }
+ },
+ "implies": [
+ {
+ "id": FAKE_ROLE_ID_2,
+ "name": FAKE_ROLE_NAME_2,
+ "links": {
+ "self": "http://example.com/identity/v3/roles/%s" % (
+ FAKE_ROLE_ID_2)
+ }
+ },
+ {
+ "id": "3",
+ "name": "test3",
+ "links": {
+ "self": "http://example.com/identity/v3/roles/3"
+ }
+ }
+ ]
+ },
+ "links": {
+ "self": "http://example.com/identity/v3/roles/"
+ "%s/implies" % FAKE_ROLE_ID
+ }
+ }
+
def setUp(self):
super(TestRolesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -172,6 +231,33 @@
domain_id="b344506af7644f6794d9cb316600b020",
group_id="123")
+ def _test_create_role_inference_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_role_inference_rule,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_ROLE_INFERENCE_RULE,
+ bytes_body,
+ status=201,
+ prior_role=self.FAKE_ROLE_ID,
+ implies_role=self.FAKE_ROLE_ID_2)
+
+ def _test_show_role_inference_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_role_inference_rule,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ROLE_INFERENCE_RULE,
+ bytes_body,
+ prior_role=self.FAKE_ROLE_ID,
+ implies_role=self.FAKE_ROLE_ID_2)
+
+ def _test_list_role_inferences_rules(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_role_inferences_rules,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLE_INFERENCES_RULES,
+ bytes_body,
+ prior_role=self.FAKE_ROLE_ID)
+
def test_create_role_with_str_body(self):
self._test_create_role()
@@ -319,3 +405,39 @@
group_id="123",
role_id="1234",
status=204)
+
+ def test_create_role_inference_rule_with_str_body(self):
+ self._test_create_role_inference_rule()
+
+ def test_create_role_inference_rule_with_bytes_body(self):
+ self._test_create_role_inference_rule(bytes_body=True)
+
+ def test_show_role_inference_rule_with_str_body(self):
+ self._test_show_role_inference_rule()
+
+ def test_show_role_inference_rule_with_bytes_body(self):
+ self._test_show_role_inference_rule(bytes_body=True)
+
+ def test_list_role_inferences_rules_with_str_body(self):
+ self._test_list_role_inferences_rules()
+
+ def test_list_role_inferences_rules_with_bytes_body(self):
+ self._test_list_role_inferences_rules(bytes_body=True)
+
+ def test_check_role_inference_rule(self):
+ self.check_service_client_function(
+ self.client.check_role_inference_rule,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ status=204,
+ prior_role=self.FAKE_ROLE_ID,
+ implies_role=self.FAKE_ROLE_ID_2)
+
+ def test_delete_role_inference_rule(self):
+ self.check_service_client_function(
+ self.client.delete_role_inference_rule,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ prior_role=self.FAKE_ROLE_ID,
+ implies_role=self.FAKE_ROLE_ID_2)