blob: 371b506f7a5e8e32beb3882ba4f5225ebdee3ed2 [file] [log] [blame]
Matt Riedemann342b37c2016-09-21 15:38:12 -04001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
jeremy.zhangc9d58002017-03-22 11:03:54 +080013import time
14
Matt Riedemann342b37c2016-09-21 15:38:12 -040015from tempest.api.compute import base
Andrea Frittolicd368412017-08-14 21:37:56 +010016from tempest.common import utils
Matt Riedemann342b37c2016-09-21 15:38:12 -040017from tempest.common import waiters
18from tempest import config
Ken'ichi Ohmichiebbfd1c2017-01-27 16:37:00 -080019from tempest.lib import decorators
jeremy.zhangc9d58002017-03-22 11:03:54 +080020from tempest.lib import exceptions as lib_exc
Matt Riedemann342b37c2016-09-21 15:38:12 -040021
22CONF = config.CONF
23
24
Steve Noyesdfade252018-01-12 14:54:57 -050025class TestVolumeSwapBase(base.BaseV2ComputeAdminTest):
Matt Riedemann342b37c2016-09-21 15:38:12 -040026
27 @classmethod
28 def skip_checks(cls):
Steve Noyesdfade252018-01-12 14:54:57 -050029 super(TestVolumeSwapBase, cls).skip_checks()
Matt Riedemann342b37c2016-09-21 15:38:12 -040030 if not CONF.compute_feature_enabled.swap_volume:
31 raise cls.skipException("Swapping volumes is not supported.")
32
Steve Noyesdfade252018-01-12 14:54:57 -050033 def wait_for_server_volume_swap(self, server_id, old_volume_id,
34 new_volume_id):
jeremy.zhangc9d58002017-03-22 11:03:54 +080035 """Waits for a server to swap the old volume to a new one."""
36 volume_attachments = self.servers_client.list_volume_attachments(
37 server_id)['volumeAttachments']
38 attached_volume_ids = [attachment['volumeId']
39 for attachment in volume_attachments]
40 start = int(time.time())
41
42 while (old_volume_id in attached_volume_ids) \
43 or (new_volume_id not in attached_volume_ids):
44 time.sleep(self.servers_client.build_interval)
45 volume_attachments = self.servers_client.list_volume_attachments(
46 server_id)['volumeAttachments']
47 attached_volume_ids = [attachment['volumeId']
48 for attachment in volume_attachments]
49
50 if int(time.time()) - start >= self.servers_client.build_timeout:
51 old_vol_bdm_status = 'in BDM' \
52 if old_volume_id in attached_volume_ids else 'not in BDM'
53 new_vol_bdm_status = 'in BDM' \
54 if new_volume_id in attached_volume_ids else 'not in BDM'
55 message = ('Failed to swap old volume %(old_volume_id)s '
56 '(current %(old_vol_bdm_status)s) to new volume '
57 '%(new_volume_id)s (current %(new_vol_bdm_status)s)'
58 ' on server %(server_id)s within the required time '
59 '(%(timeout)s s)' %
60 {'old_volume_id': old_volume_id,
61 'old_vol_bdm_status': old_vol_bdm_status,
62 'new_volume_id': new_volume_id,
63 'new_vol_bdm_status': new_vol_bdm_status,
64 'server_id': server_id,
65 'timeout': self.servers_client.build_timeout})
66 raise lib_exc.TimeoutException(message)
67
Steve Noyesdfade252018-01-12 14:54:57 -050068
69class TestVolumeSwap(TestVolumeSwapBase):
70 """The test suite for swapping of volume with admin user.
71
72 The following is the scenario outline:
Sergey Vilgelmeac094a2018-11-21 18:27:51 -060073
Steve Noyesdfade252018-01-12 14:54:57 -050074 1. Create a volume "volume1" with non-admin.
75 2. Create a volume "volume2" with non-admin.
76 3. Boot an instance "instance1" with non-admin.
77 4. Attach "volume1" to "instance1" with non-admin.
78 5. Swap volume from "volume1" to "volume2" as admin.
79 6. Check the swap volume is successful and "volume2"
80 is attached to "instance1" and "volume1" is in available state.
81 7. Swap volume from "volume2" to "volume1" as admin.
82 8. Check the swap volume is successful and "volume1"
83 is attached to "instance1" and "volume2" is in available state.
84 """
85
Matt Riedemannb5720532018-09-19 16:02:05 -040086 # NOTE(mriedem): This is an uncommon scenario to call the compute API
87 # to swap volumes directly; swap volume is primarily only for volume
88 # live migration and retype callbacks from the volume service, and is slow
89 # so it's marked as such.
90 @decorators.attr(type='slow')
Ken'ichi Ohmichiebbfd1c2017-01-27 16:37:00 -080091 @decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813')
Andrea Frittolicd368412017-08-14 21:37:56 +010092 @utils.services('volume')
Matt Riedemann342b37c2016-09-21 15:38:12 -040093 def test_volume_swap(self):
94 # Create two volumes.
95 # NOTE(gmann): Volumes are created before server creation so that
96 # volumes cleanup can happen successfully irrespective of which volume
97 # is attached to server.
98 volume1 = self.create_volume()
99 volume2 = self.create_volume()
100 # Boot server
101 server = self.create_test_server(wait_until='ACTIVE')
102 # Attach "volume1" to server
103 self.attach_volume(server, volume1)
104 # Swap volume from "volume1" to "volume2"
zhufle5b62a62017-02-15 16:04:21 +0800105 self.admin_servers_client.update_attached_volume(
Matt Riedemann342b37c2016-09-21 15:38:12 -0400106 server['id'], volume1['id'], volumeId=volume2['id'])
lkuchlan52d7b0d2016-11-07 20:53:19 +0200107 waiters.wait_for_volume_resource_status(self.volumes_client,
108 volume1['id'], 'available')
109 waiters.wait_for_volume_resource_status(self.volumes_client,
110 volume2['id'], 'in-use')
Steve Noyesdfade252018-01-12 14:54:57 -0500111 self.wait_for_server_volume_swap(server['id'], volume1['id'],
112 volume2['id'])
Matt Riedemann342b37c2016-09-21 15:38:12 -0400113 # Verify "volume2" is attached to the server
114 vol_attachments = self.servers_client.list_volume_attachments(
115 server['id'])['volumeAttachments']
116 self.assertEqual(1, len(vol_attachments))
117 self.assertIn(volume2['id'], vol_attachments[0]['volumeId'])
118
Lee Yarwood89a6cfc2017-02-01 16:01:14 +0000119 # Swap volume from "volume2" to "volume1"
120 self.admin_servers_client.update_attached_volume(
121 server['id'], volume2['id'], volumeId=volume1['id'])
122 waiters.wait_for_volume_resource_status(self.volumes_client,
123 volume2['id'], 'available')
124 waiters.wait_for_volume_resource_status(self.volumes_client,
125 volume1['id'], 'in-use')
Steve Noyesdfade252018-01-12 14:54:57 -0500126 self.wait_for_server_volume_swap(server['id'], volume2['id'],
127 volume1['id'])
Lee Yarwood89a6cfc2017-02-01 16:01:14 +0000128 # Verify "volume1" is attached to the server
129 vol_attachments = self.servers_client.list_volume_attachments(
130 server['id'])['volumeAttachments']
131 self.assertEqual(1, len(vol_attachments))
132 self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])
Steve Noyesdfade252018-01-12 14:54:57 -0500133
134
zhuflfdee0652018-03-09 10:38:31 +0800135class TestMultiAttachVolumeSwap(TestVolumeSwapBase):
Steve Noyesdfade252018-01-12 14:54:57 -0500136 min_microversion = '2.60'
137 max_microversion = 'latest'
138
139 @classmethod
140 def skip_checks(cls):
zhuflfdee0652018-03-09 10:38:31 +0800141 super(TestMultiAttachVolumeSwap, cls).skip_checks()
Steve Noyesdfade252018-01-12 14:54:57 -0500142 if not CONF.compute_feature_enabled.volume_multiattach:
143 raise cls.skipException('Volume multi-attach is not available.')
144
Matt Riedemann1bbfa122018-06-06 18:27:17 -0400145 @classmethod
146 def setup_clients(cls):
147 super(TestMultiAttachVolumeSwap, cls).setup_clients()
148 # Need this to set readonly volumes.
149 cls.admin_volumes_client = cls.os_admin.volumes_client_latest
150
Matt Riedemannb5720532018-09-19 16:02:05 -0400151 # NOTE(mriedem): This is an uncommon scenario to call the compute API
152 # to swap volumes directly; swap volume is primarily only for volume
153 # live migration and retype callbacks from the volume service, and is slow
154 # so it's marked as such.
155 @decorators.attr(type='slow')
Steve Noyesdfade252018-01-12 14:54:57 -0500156 @decorators.idempotent_id('e8f8f9d1-d7b7-4cd2-8213-ab85ef697b6e')
Matt Riedemann7581e992018-10-01 11:33:34 -0400157 # For some reason this test intermittently fails on teardown when there are
158 # multiple compute nodes and the servers are split across the computes.
159 # For now, just skip this test if there are multiple computes.
160 # Alternatively we could put the servers in an affinity group if there are
161 # multiple computes but that would just side-step the underlying bug.
162 @decorators.skip_because(bug='1807723',
163 condition=CONF.compute.min_compute_nodes > 1)
Steve Noyesdfade252018-01-12 14:54:57 -0500164 @utils.services('volume')
165 def test_volume_swap_with_multiattach(self):
166 # Create two volumes.
167 # NOTE(gmann): Volumes are created before server creation so that
168 # volumes cleanup can happen successfully irrespective of which volume
169 # is attached to server.
170 volume1 = self.create_volume(multiattach=True)
Matt Riedemann1bbfa122018-06-06 18:27:17 -0400171 # Make volume1 read-only since you can't swap from a volume with
172 # multiple read/write attachments, and you can't change the readonly
173 # flag on an in-use volume so we have to do this before attaching
174 # volume1 to anything. If the compute API ever supports per-attachment
175 # attach modes, then we can handle this differently.
176 self.admin_volumes_client.update_volume_readonly(
177 volume1['id'], readonly=True)
Steve Noyesdfade252018-01-12 14:54:57 -0500178 volume2 = self.create_volume(multiattach=True)
179
Matt Riedemann882b4eb2018-09-20 09:59:11 -0400180 # Create two servers and wait for them to be ACTIVE.
181 reservation_id = self.create_test_server(
182 wait_until='ACTIVE', min_count=2,
183 return_reservation_id=True)['reservation_id']
184 # Get the servers using the reservation_id.
185 servers = self.servers_client.list_servers(
186 reservation_id=reservation_id)['servers']
187 self.assertEqual(2, len(servers))
Steve Noyesdfade252018-01-12 14:54:57 -0500188 # Attach volume1 to server1
Matt Riedemann882b4eb2018-09-20 09:59:11 -0400189 server1 = servers[0]
Steve Noyesdfade252018-01-12 14:54:57 -0500190 self.attach_volume(server1, volume1)
Steve Noyesdfade252018-01-12 14:54:57 -0500191 # Attach volume1 to server2
Matt Riedemann882b4eb2018-09-20 09:59:11 -0400192 server2 = servers[1]
Steve Noyesdfade252018-01-12 14:54:57 -0500193 self.attach_volume(server2, volume1)
194
195 # Swap volume1 to volume2 on server1, volume1 should remain attached
196 # to server 2
197 self.admin_servers_client.update_attached_volume(
198 server1['id'], volume1['id'], volumeId=volume2['id'])
199 # volume1 will return to in-use after the swap
200 waiters.wait_for_volume_resource_status(self.volumes_client,
201 volume1['id'], 'in-use')
202 waiters.wait_for_volume_resource_status(self.volumes_client,
203 volume2['id'], 'in-use')
204 self.wait_for_server_volume_swap(server1['id'], volume1['id'],
205 volume2['id'])
206
207 # Verify volume2 is attached to server1
208 vol_attachments = self.servers_client.list_volume_attachments(
209 server1['id'])['volumeAttachments']
210 self.assertEqual(1, len(vol_attachments))
211 self.assertIn(volume2['id'], vol_attachments[0]['volumeId'])
212
213 # Verify volume1 is still attached to server2
214 vol_attachments = self.servers_client.list_volume_attachments(
215 server2['id'])['volumeAttachments']
216 self.assertEqual(1, len(vol_attachments))
217 self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])