Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 1 | # 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.zhang | c9d5800 | 2017-03-22 11:03:54 +0800 | [diff] [blame] | 13 | import time |
| 14 | |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 15 | from tempest.api.compute import base |
Andrea Frittoli | cd36841 | 2017-08-14 21:37:56 +0100 | [diff] [blame] | 16 | from tempest.common import utils |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 17 | from tempest.common import waiters |
| 18 | from tempest import config |
Ken'ichi Ohmichi | ebbfd1c | 2017-01-27 16:37:00 -0800 | [diff] [blame] | 19 | from tempest.lib import decorators |
jeremy.zhang | c9d5800 | 2017-03-22 11:03:54 +0800 | [diff] [blame] | 20 | from tempest.lib import exceptions as lib_exc |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 21 | |
| 22 | CONF = config.CONF |
| 23 | |
| 24 | |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 25 | class TestVolumeSwapBase(base.BaseV2ComputeAdminTest): |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 26 | |
| 27 | @classmethod |
| 28 | def skip_checks(cls): |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 29 | super(TestVolumeSwapBase, cls).skip_checks() |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 30 | if not CONF.compute_feature_enabled.swap_volume: |
| 31 | raise cls.skipException("Swapping volumes is not supported.") |
| 32 | |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 33 | def wait_for_server_volume_swap(self, server_id, old_volume_id, |
| 34 | new_volume_id): |
jeremy.zhang | c9d5800 | 2017-03-22 11:03:54 +0800 | [diff] [blame] | 35 | """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 Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 68 | |
| 69 | class TestVolumeSwap(TestVolumeSwapBase): |
| 70 | """The test suite for swapping of volume with admin user. |
| 71 | |
| 72 | The following is the scenario outline: |
Sergey Vilgelm | eac094a | 2018-11-21 18:27:51 -0600 | [diff] [blame] | 73 | |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 74 | 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 Riedemann | b572053 | 2018-09-19 16:02:05 -0400 | [diff] [blame] | 86 | # 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 Ohmichi | ebbfd1c | 2017-01-27 16:37:00 -0800 | [diff] [blame] | 91 | @decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813') |
Andrea Frittoli | cd36841 | 2017-08-14 21:37:56 +0100 | [diff] [blame] | 92 | @utils.services('volume') |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 93 | 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" |
zhufl | e5b62a6 | 2017-02-15 16:04:21 +0800 | [diff] [blame] | 105 | self.admin_servers_client.update_attached_volume( |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 106 | server['id'], volume1['id'], volumeId=volume2['id']) |
lkuchlan | 52d7b0d | 2016-11-07 20:53:19 +0200 | [diff] [blame] | 107 | 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 Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 111 | self.wait_for_server_volume_swap(server['id'], volume1['id'], |
| 112 | volume2['id']) |
Matt Riedemann | 342b37c | 2016-09-21 15:38:12 -0400 | [diff] [blame] | 113 | # 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 Yarwood | 89a6cfc | 2017-02-01 16:01:14 +0000 | [diff] [blame] | 119 | # 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 Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 126 | self.wait_for_server_volume_swap(server['id'], volume2['id'], |
| 127 | volume1['id']) |
Lee Yarwood | 89a6cfc | 2017-02-01 16:01:14 +0000 | [diff] [blame] | 128 | # 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 Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 133 | |
| 134 | |
zhufl | fdee065 | 2018-03-09 10:38:31 +0800 | [diff] [blame] | 135 | class TestMultiAttachVolumeSwap(TestVolumeSwapBase): |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 136 | min_microversion = '2.60' |
| 137 | max_microversion = 'latest' |
| 138 | |
| 139 | @classmethod |
| 140 | def skip_checks(cls): |
zhufl | fdee065 | 2018-03-09 10:38:31 +0800 | [diff] [blame] | 141 | super(TestMultiAttachVolumeSwap, cls).skip_checks() |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 142 | if not CONF.compute_feature_enabled.volume_multiattach: |
| 143 | raise cls.skipException('Volume multi-attach is not available.') |
| 144 | |
Matt Riedemann | b572053 | 2018-09-19 16:02:05 -0400 | [diff] [blame] | 145 | # NOTE(mriedem): This is an uncommon scenario to call the compute API |
| 146 | # to swap volumes directly; swap volume is primarily only for volume |
| 147 | # live migration and retype callbacks from the volume service, and is slow |
| 148 | # so it's marked as such. |
| 149 | @decorators.attr(type='slow') |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 150 | @decorators.idempotent_id('e8f8f9d1-d7b7-4cd2-8213-ab85ef697b6e') |
Matt Riedemann | 7581e99 | 2018-10-01 11:33:34 -0400 | [diff] [blame] | 151 | # For some reason this test intermittently fails on teardown when there are |
| 152 | # multiple compute nodes and the servers are split across the computes. |
| 153 | # For now, just skip this test if there are multiple computes. |
| 154 | # Alternatively we could put the servers in an affinity group if there are |
| 155 | # multiple computes but that would just side-step the underlying bug. |
| 156 | @decorators.skip_because(bug='1807723', |
| 157 | condition=CONF.compute.min_compute_nodes > 1) |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 158 | @utils.services('volume') |
| 159 | def test_volume_swap_with_multiattach(self): |
| 160 | # Create two volumes. |
| 161 | # NOTE(gmann): Volumes are created before server creation so that |
| 162 | # volumes cleanup can happen successfully irrespective of which volume |
| 163 | # is attached to server. |
| 164 | volume1 = self.create_volume(multiattach=True) |
| 165 | volume2 = self.create_volume(multiattach=True) |
| 166 | |
Matt Riedemann | 882b4eb | 2018-09-20 09:59:11 -0400 | [diff] [blame] | 167 | # Create two servers and wait for them to be ACTIVE. |
| 168 | reservation_id = self.create_test_server( |
| 169 | wait_until='ACTIVE', min_count=2, |
| 170 | return_reservation_id=True)['reservation_id'] |
| 171 | # Get the servers using the reservation_id. |
| 172 | servers = self.servers_client.list_servers( |
| 173 | reservation_id=reservation_id)['servers'] |
| 174 | self.assertEqual(2, len(servers)) |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 175 | # Attach volume1 to server1 |
Matt Riedemann | 882b4eb | 2018-09-20 09:59:11 -0400 | [diff] [blame] | 176 | server1 = servers[0] |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 177 | self.attach_volume(server1, volume1) |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 178 | # Attach volume1 to server2 |
Matt Riedemann | 882b4eb | 2018-09-20 09:59:11 -0400 | [diff] [blame] | 179 | server2 = servers[1] |
Steve Noyes | dfade25 | 2018-01-12 14:54:57 -0500 | [diff] [blame] | 180 | self.attach_volume(server2, volume1) |
| 181 | |
| 182 | # Swap volume1 to volume2 on server1, volume1 should remain attached |
| 183 | # to server 2 |
| 184 | self.admin_servers_client.update_attached_volume( |
| 185 | server1['id'], volume1['id'], volumeId=volume2['id']) |
| 186 | # volume1 will return to in-use after the swap |
| 187 | waiters.wait_for_volume_resource_status(self.volumes_client, |
| 188 | volume1['id'], 'in-use') |
| 189 | waiters.wait_for_volume_resource_status(self.volumes_client, |
| 190 | volume2['id'], 'in-use') |
| 191 | self.wait_for_server_volume_swap(server1['id'], volume1['id'], |
| 192 | volume2['id']) |
| 193 | |
| 194 | # Verify volume2 is attached to server1 |
| 195 | vol_attachments = self.servers_client.list_volume_attachments( |
| 196 | server1['id'])['volumeAttachments'] |
| 197 | self.assertEqual(1, len(vol_attachments)) |
| 198 | self.assertIn(volume2['id'], vol_attachments[0]['volumeId']) |
| 199 | |
| 200 | # Verify volume1 is still attached to server2 |
| 201 | vol_attachments = self.servers_client.list_volume_attachments( |
| 202 | server2['id'])['volumeAttachments'] |
| 203 | self.assertEqual(1, len(vol_attachments)) |
| 204 | self.assertIn(volume1['id'], vol_attachments[0]['volumeId']) |