Merge "Enhance validation decorator with error code"
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index abbb435..18f132e 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -87,12 +87,18 @@
             raise
 
         finally:
-            if BaseTestCase.get_identity_version() != 'v3':
-                test_obj.auth_provider.clear_auth()
-                # Sleep to avoid 401 errors caused by rounding in timing of
-                # fernet token creation.
-                time.sleep(1)
-                test_obj.auth_provider.set_auth()
+            # NOTE(felipemonteiro): These two comments below are copied from
+            # tempest.api.identity.v2/v3.test_users.
+            #
+            # Reset auth again to verify the password restore does work.
+            # Clear auth restores the original credentials and deletes
+            # cached auth data.
+            test_obj.auth_provider.clear_auth()
+            # Fernet tokens are not subsecond aware and Keystone should only be
+            # precise to the second. Sleep to ensure we are passing the second
+            # boundary before attempting to authenticate.
+            time.sleep(1)
+            test_obj.auth_provider.set_auth()
 
     def _clear_user_roles(cls, user_id, tenant_id):
         roles = cls.creds_client.roles_client.list_user_roles_on_project(
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
index 0e1b00b..d466ded 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
@@ -18,6 +18,8 @@
 
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest import test
@@ -36,17 +38,34 @@
     def setup_clients(cls):
         super(ServerActionsRbacTest, cls).setup_clients()
         cls.client = cls.servers_client
+        cls.snapshots_client = cls.snapshots_extensions_client
 
     @classmethod
     def resource_setup(cls):
         cls.set_validation_resources()
         super(ServerActionsRbacTest, cls).resource_setup()
+        # Create test server
         cls.server_id = cls.create_test_server(wait_until='ACTIVE',
                                                validatable=True)['id']
         cls.flavor_ref = CONF.compute.flavor_ref
         cls.flavor_ref_alt = CONF.compute.flavor_ref_alt
         cls.image_ref = CONF.compute.image_ref
 
+        # Create a volume
+        volume_name = data_utils.rand_name(cls.__name__ + '-volume')
+        name_field = 'name'
+        if not CONF.volume_feature_enabled.api_v2:
+            name_field = 'display_name'
+
+        params = {name_field: volume_name,
+                  'imageRef': CONF.compute.image_ref,
+                  'size': CONF.volume.volume_size}
+        volume = cls.volumes_client.create_volume(**params)['volume']
+        waiters.wait_for_volume_resource_status(cls.volumes_client,
+                                                volume['id'], 'available')
+        cls.volumes.append(volume)
+        cls.volume_id = volume['id']
+
     def setUp(self):
         super(ServerActionsRbacTest, self).setUp()
         try:
@@ -64,6 +83,56 @@
             self.__class__.server_id = self.rebuild_server(
                 self.server_id, validatable=True)
 
+    @classmethod
+    def resource_cleanup(cls):
+        # If a test case creates an image from a server that is created with
+        # a volume, a volume snapshot will automatically be created by default.
+        # We need to delete the volume snapshot.
+        try:
+            body = cls.snapshots_extensions_client.list_snapshots()
+            volume_snapshots = body['snapshots']
+        except Exception:
+            LOG.info("Cannot retrieve snapshots for cleanup.")
+        else:
+            for snapshot in volume_snapshots:
+                if snapshot['volumeId'] == cls.volume_id:
+                    # Wait for snapshot status to become 'available' before
+                    # deletion
+                    waiters.wait_for_volume_resource_status(
+                        cls.snapshots_client, snapshot['id'], 'available')
+                    test_utils.call_and_ignore_notfound_exc(
+                        cls.snapshots_client.delete_snapshot, snapshot['id'])
+
+            for snapshot in volume_snapshots:
+                if snapshot['volumeId'] == cls.volume_id:
+                    test_utils.call_and_ignore_notfound_exc(
+                        cls.snapshots_client.wait_for_resource_deletion,
+                        snapshot['id'])
+
+        super(ServerActionsRbacTest, cls).resource_cleanup()
+
+    def _create_test_server_with_volume(self, volume_id):
+        # Create a server with the volume created earlier
+        server_name = data_utils.rand_name(self.__class__.__name__ + "-server")
+        bd_map_v2 = [{'uuid': volume_id,
+                      'source_type': 'volume',
+                      'destination_type': 'volume',
+                      'boot_index': 0,
+                      'delete_on_termination': True}]
+        device_mapping = {'block_device_mapping_v2': bd_map_v2}
+
+        # Since the server is booted from volume, the imageRef does not need
+        # to be specified.
+        server = self.client.create_server(name=server_name,
+                                           imageRef='',
+                                           flavorRef=CONF.compute.flavor_ref,
+                                           **device_mapping)['server']
+
+        waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
+
+        self.servers.append(server)
+        return server
+
     def _test_start_server(self):
         self.client.start_server(self.server_id)
         waiters.wait_for_server_status(self.client, self.server_id,
@@ -206,6 +275,29 @@
         self.rbac_utils.switch_role(self, switchToRbacRole=True)
         self.client.show_server(self.server_id)
 
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:servers:create_image")
+    @decorators.idempotent_id('ba0ac859-99f4-4055-b5e0-e0905a44d331')
+    def test_create_image(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+
+        # This function will also call show image
+        self.create_image_from_server(self.server_id,
+                                      wait_until='ACTIVE')
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:servers:create_image:allow_volume_backed")
+    @decorators.idempotent_id('8b869f73-49b3-4cc4-a0ce-ef64f8e1d6f9')
+    def test_create_image_volume_backed(self):
+        server = self._create_test_server_with_volume(self.volume_id)
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+
+        # This function will also call show image
+        self.create_image_from_server(server['id'],
+                                      wait_until='ACTIVE')
+
 
 class ServerActionsV216RbacTest(rbac_base.BaseV2ComputeRbacTest):
 
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py
index 66798cd..3ae4c21 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py
@@ -26,6 +26,10 @@
 class IdentityUserV3AdminRbacTest(
         rbac_base.BaseIdentityV3RbacAdminTest):
 
+    def setUp(self):
+        super(IdentityUserV3AdminRbacTest, self).setUp()
+        self.default_user_id = self.auth_provider.credentials.user_id
+
     @rbac_rule_validation.action(service="keystone",
                                  rule="identity:create_user")
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d904')
@@ -82,16 +86,13 @@
     @rbac_rule_validation.action(service="keystone",
                                  rule="identity:get_user")
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d908')
-    def test_show_user(self):
+    def test_show_own_user(self):
         """Get one user.
 
         RBAC test for Keystone: identity:get_user
         """
-        user_name = data_utils.rand_name('test_get_user')
-        user = self._create_test_user(name=user_name, password=None)
-
         self.rbac_utils.switch_role(self, switchToRbacRole=True)
-        self.non_admin_users_client.show_user(user['id'])
+        self.non_admin_users_client.show_user(self.default_user_id)
 
     @rbac_rule_validation.action(service="keystone",
                                  rule="identity:change_password")
@@ -102,37 +103,33 @@
         RBAC test for Keystone: identity:change_password
         """
         user_name = data_utils.rand_name('test_change_password')
-        user = self._create_test_user(name=user_name, password='nova')
+        original_password = data_utils.rand_password()
+        user = self._create_test_user(name=user_name,
+                                      password=original_password)
 
         self.rbac_utils.switch_role(self, switchToRbacRole=True)
-        self.non_admin_users_client \
-            .update_user_password(user['id'],
-                                  original_password='nova',
-                                  password='neutron')
+        self.non_admin_users_client.update_user_password(
+            user['id'], original_password=original_password,
+            password=data_utils.rand_password())
 
     @rbac_rule_validation.action(service="keystone",
                                  rule="identity:list_groups_for_user")
     @decorators.idempotent_id('bd5946d4-46d2-423d-a800-a3e7aabc18b3')
-    def test_list_group_user(self):
+    def test_list_own_user_group(self):
         """Lists groups which a user belongs to.
 
         RBAC test for Keystone: identity:list_groups_for_user
         """
-        user_name = data_utils.rand_name('User')
-        user = self._create_test_user(name=user_name, password=None)
-
         self.rbac_utils.switch_role(self, switchToRbacRole=True)
-        self.non_admin_users_client.list_user_groups(user['id'])
+        self.non_admin_users_client.list_user_groups(self.default_user_id)
 
     @rbac_rule_validation.action(service="keystone",
                                  rule="identity:list_user_projects")
     @decorators.idempotent_id('0f148510-63bf-11e6-1564-080044d0d909')
-    def test_list_user_projects(self):
+    def test_list_own_user_projects(self):
         """List User's Projects.
 
         RBAC test for Keystone: identity:list_user_projects
         """
-        user = self.setup_test_user()
-
         self.rbac_utils.switch_role(self, switchToRbacRole=True)
-        self.non_admin_users_client.list_user_projects(user['id'])
+        self.non_admin_users_client.list_user_projects(self.default_user_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
index 203384e..69aa32a 100644
--- a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
@@ -94,6 +94,7 @@
         create_router:external_gateway_info:external_fixed_ips policy
         """
         name = data_utils.rand_name('snat-router')
+
         # Pick an ip address within the allocation_pools range
         ip_address = random.choice(list(self.admin_ip_range))
         external_fixed_ips = {'subnet_id': self.admin_subnet['id'],
@@ -161,6 +162,10 @@
         self.routers_client.update_router(
             self.admin_router['id'],
             external_gateway_info={'network_id': self.admin_network['id']})
+        self.addCleanup(
+            self.routers_client.update_router,
+            self.admin_router['id'],
+            external_gateway_info=None)
 
     @rbac_rule_validation.action(
         service="neutron",
@@ -177,6 +182,10 @@
             self.admin_router['id'],
             external_gateway_info={'network_id': self.admin_network['id'],
                                    'enable_snat': True})
+        self.addCleanup(
+            self.routers_client.update_router,
+            self.admin_router['id'],
+            external_gateway_info=None)
 
     @rbac_rule_validation.action(
         service="neutron",