blob: 076b835d49223aff8ac126b7d84a04a24f00094f [file] [log] [blame]
Gorka Eguileorcbaf22e2023-04-20 17:09:43 +02001# Copyright 2023 Red Hat
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from unittest import mock
17
18from tempest.common import utils
19from tempest.common import waiters
20from tempest import config
21from tempest.lib import decorators
22from tempest.lib import exceptions
23from tempest.scenario import manager
24
25CONF = config.CONF
26
27
28class BaseAttachmentTest(manager.ScenarioTest):
Ghanshyam Mann2803b572023-08-04 12:11:59 -070029
30 @classmethod
31 def skip_checks(cls):
32 super(BaseAttachmentTest, cls).skip_checks()
33 if not CONF.service_available.cinder:
34 raise cls.skipException("Cinder is not available")
35
Gorka Eguileorcbaf22e2023-04-20 17:09:43 +020036 @classmethod
37 def setup_clients(cls):
38 super().setup_clients()
39 cls.attachments_client = cls.os_primary.attachments_client_latest
40 cls.admin_volume_client = cls.os_admin.volumes_client_latest
41
42 def _call_with_fake_service_token(self, valid_token,
43 client, method_name, *args, **kwargs):
44 """Call client method with non-service service token
45
46 Add a service token header that can be a valid normal user token (which
47 won't have the service role) or an invalid token altogether.
48 """
49 original_raw_request = client.raw_request
50
51 def raw_request(url, method, headers=None, body=None, chunked=False,
52 log_req_body=None):
53 token = headers['X-Auth-Token']
54 if not valid_token:
55 token = token[:-1] + ('a' if token[-1] != 'a' else 'b')
56 headers['X-Service-Token'] = token
57 return original_raw_request(url, method, headers=headers,
58 body=body, chunked=chunked,
59 log_req_body=log_req_body)
60
61 client_method = getattr(client, method_name)
62 with mock.patch.object(client, 'raw_request', raw_request):
63 return client_method(*args, **kwargs)
64
65
66class TestServerVolumeAttachmentScenario(BaseAttachmentTest):
67
68 """Test server attachment behaviors
69
70 This tests that volume attachments to servers may not be removed directly
71 and are only allowed through the compute service (bug #2004555).
72 """
73
74 @decorators.attr(type='slow')
75 @decorators.idempotent_id('be615530-f105-437a-8afe-ce998c9535d9')
76 @utils.services('compute', 'volume', 'image', 'network')
77 def test_server_detach_rules(self):
78 """Test that various methods of detaching a volume honors the rules"""
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -050079 volume = self.create_volume(wait_until=None)
80 volume2 = self.create_volume(wait_until=None)
81
Gorka Eguileorcbaf22e2023-04-20 17:09:43 +020082 server = self.create_server(wait_until='SSHABLE')
83 servers = self.servers_client.list_servers()['servers']
84 self.assertIn(server['id'], [x['id'] for x in servers])
85
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -050086 waiters.wait_for_volume_resource_status(self.volumes_client,
87 volume['id'], 'available')
88 # The volume retrieved on creation has a non-up-to-date status.
89 # Retrieval after it becomes active ensures correct details.
90 volume = self.volumes_client.show_volume(volume['id'])['volume']
Gorka Eguileorcbaf22e2023-04-20 17:09:43 +020091
92 volume = self.nova_volume_attach(server, volume)
93 self.addCleanup(self.nova_volume_detach, server, volume)
94 att_id = volume['attachments'][0]['attachment_id']
95
96 # Test user call to detach volume is rejected
97 self.assertRaises((exceptions.Forbidden, exceptions.Conflict),
98 self.volumes_client.detach_volume, volume['id'])
99
100 # Test user call to terminate connection is rejected
101 self.assertRaises((exceptions.Forbidden, exceptions.Conflict),
102 self.volumes_client.terminate_connection,
103 volume['id'], connector={})
104
105 # Test faking of service token on call to detach, force detach,
106 # terminate_connection
107 for valid_token in (True, False):
108 valid_exceptions = [exceptions.Forbidden, exceptions.Conflict]
109 if not valid_token:
110 valid_exceptions.append(exceptions.Unauthorized)
111 self.assertRaises(
112 tuple(valid_exceptions),
113 self._call_with_fake_service_token,
114 valid_token,
115 self.volumes_client,
116 'detach_volume',
117 volume['id'])
118 self.assertRaises(
119 tuple(valid_exceptions),
120 self._call_with_fake_service_token,
121 valid_token,
122 self.volumes_client,
123 'terminate_connection',
124 volume['id'], connector={})
125
126 # Reset volume's status to error
127 self.admin_volume_client.reset_volume_status(volume['id'],
128 status='error')
129 waiters.wait_for_volume_resource_status(self.volumes_client,
130 volume['id'], 'error')
131
132 # For the cleanup, we need to reset the volume status to in-use before
133 # the other cleanup steps try to detach it.
134 self.addCleanup(waiters.wait_for_volume_resource_status,
135 self.volumes_client, volume['id'], 'in-use')
136 self.addCleanup(self.admin_volume_client.reset_volume_status,
137 volume['id'], status='in-use')
138
139 # Test user call to force detach volume is rejected
140 self.assertRaises(
141 (exceptions.Forbidden, exceptions.Conflict),
142 self.admin_volume_client.force_detach_volume,
143 volume['id'], connector=None,
144 attachment_id=att_id)
145
146 # Test trying to override detach with force and service token
147 for valid_token in (True, False):
148 valid_exceptions = [exceptions.Forbidden, exceptions.Conflict]
149 if not valid_token:
150 valid_exceptions.append(exceptions.Unauthorized)
151 self.assertRaises(
152 tuple(valid_exceptions),
153 self._call_with_fake_service_token,
154 valid_token,
155 self.admin_volume_client,
156 'force_detach_volume',
157 volume['id'], connector=None, attachment_id=att_id)
158
159 # Test user call to detach with mismatch is rejected
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -0500160 waiters.wait_for_volume_resource_status(self.volumes_client,
161 volume2['id'], 'available')
162 # The volume retrieved on creation has a non-up-to-date status.
163 # Retrieval after it becomes active ensures correct details.
164 volume2 = self.volumes_client.show_volume(volume2['id'])['volume']
165
Gorka Eguileorcbaf22e2023-04-20 17:09:43 +0200166 volume2 = self.nova_volume_attach(server, volume2)
167 att_id2 = volume2['attachments'][0]['attachment_id']
168 self.assertRaises(
169 (exceptions.Forbidden, exceptions.BadRequest),
170 self.volumes_client.detach_volume,
171 volume['id'], attachment_id=att_id2)
172
173
174class TestServerVolumeAttachScenarioOldVersion(BaseAttachmentTest):
175 volume_min_microversion = '3.27'
176 volume_max_microversion = 'latest'
177
178 @decorators.attr(type='slow')
179 @decorators.idempotent_id('6f4d2144-99f4-495c-8b0b-c6a537971418')
180 @utils.services('compute', 'volume', 'image', 'network')
181 def test_old_versions_reject(self):
182 server = self.create_server(wait_until='SSHABLE')
183 servers = self.servers_client.list_servers()['servers']
184 self.assertIn(server['id'], [x['id'] for x in servers])
185
186 volume = self.create_volume()
187
188 volume = self.nova_volume_attach(server, volume)
189 self.addCleanup(self.nova_volume_detach, server, volume)
190 att_id = volume['attachments'][0]['attachment_id']
191
192 for valid_token in (True, False):
193 valid_exceptions = [exceptions.Forbidden,
194 exceptions.Conflict]
195 if not valid_token:
196 valid_exceptions.append(exceptions.Unauthorized)
197 self.assertRaises(
198 tuple(valid_exceptions),
199 self._call_with_fake_service_token,
200 valid_token,
201 self.attachments_client,
202 'delete_attachment',
203 att_id)
204
205 self.assertRaises(
206 (exceptions.Forbidden, exceptions.Conflict),
207 self.attachments_client.delete_attachment,
208 att_id)