blob: 0ee85bc334d26a84a63cba57d1759cde94dfee33 [file] [log] [blame]
dfilppi9981f552017-08-07 20:10:53 +00001#########
2# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain 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,
12# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# * See the License for the specific language governing permissions and
14# * limitations under the License.
15
16import mock
17import unittest
18
19from cloudify import mocks as cfy_mocks
20from cloudify import exceptions as cfy_exc
21from cloudify.state import current_ctx
22from cinder_plugin import volume
23from nova_plugin import server
24from openstack_plugin_common import (OPENSTACK_ID_PROPERTY,
25 OPENSTACK_TYPE_PROPERTY,
26 OPENSTACK_NAME_PROPERTY)
27
28
29class TestCinderVolume(unittest.TestCase):
30
31 def _mock(self, **kwargs):
32 ctx = cfy_mocks.MockCloudifyContext(**kwargs)
33 current_ctx.set(ctx)
34 return ctx
35
36 def tearDown(self):
37 current_ctx.clear()
38
39 def test_create_new(self):
40 volume_name = 'fake volume name'
41 volume_description = 'fake volume'
42 volume_id = '00000000-0000-0000-0000-000000000000'
43 volume_size = 10
44
45 volume_properties = {
46 'volume': {
47 'size': volume_size,
48 'description': volume_description
49 },
50 'use_external_resource': False,
51 'device_name': '/dev/fake',
52 'resource_id': volume_name,
53 }
54
55 creating_volume_m = mock.Mock()
56 creating_volume_m.id = volume_id
57 creating_volume_m.status = volume.VOLUME_STATUS_CREATING
58 available_volume_m = mock.Mock()
59 available_volume_m.id = volume_id
60 available_volume_m.status = volume.VOLUME_STATUS_AVAILABLE
61 cinder_client_m = mock.Mock()
62 cinder_client_m.volumes = mock.Mock()
63 cinder_client_m.volumes.create = mock.Mock(
64 return_value=creating_volume_m)
65 cinder_client_m.volumes.get = mock.Mock(
66 return_value=available_volume_m)
67 ctx_m = self._mock(node_id='a', properties=volume_properties)
68
69 volume.create(cinder_client=cinder_client_m, args={}, ctx=ctx_m,
70 status_attempts=10, status_timeout=2)
71
72 cinder_client_m.volumes.create.assert_called_once_with(
73 size=volume_size,
74 name=volume_name,
75 description=volume_description)
76 cinder_client_m.volumes.get.assert_called_once_with(volume_id)
77 self.assertEqual(
78 volume_id,
79 ctx_m.instance.runtime_properties[OPENSTACK_ID_PROPERTY])
80 self.assertEqual(
81 volume.VOLUME_OPENSTACK_TYPE,
82 ctx_m.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY])
83
84 def test_create_use_existing(self):
85 volume_id = '00000000-0000-0000-0000-000000000000'
86
87 volume_properties = {
88 'use_external_resource': True,
89 'device_name': '/dev/fake',
90 'resource_id': volume_id,
91 }
92 existing_volume_m = mock.Mock()
93 existing_volume_m.id = volume_id
94 existing_volume_m.status = volume.VOLUME_STATUS_AVAILABLE
95 cinder_client_m = mock.Mock()
96 cinder_client_m.volumes = mock.Mock()
97 cinder_client_m.volumes.create = mock.Mock()
98 cinder_client_m.cosmo_get_if_exists = mock.Mock(
99 return_value=existing_volume_m)
100 cinder_client_m.get_id_from_resource = mock.Mock(
101 return_value=volume_id)
102 ctx_m = self._mock(node_id='a', properties=volume_properties)
103
104 volume.create(cinder_client=cinder_client_m, args={}, ctx=ctx_m,
105 status_attempts=10, status_timeout=2)
106
107 self.assertFalse(cinder_client_m.volumes.create.called)
108 self.assertEqual(
109 volume_id,
110 ctx_m.instance.runtime_properties[OPENSTACK_ID_PROPERTY])
111 self.assertEqual(
112 volume.VOLUME_OPENSTACK_TYPE,
113 ctx_m.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY])
114
115 def test_delete(self):
116 volume_id = '00000000-0000-0000-0000-000000000000'
117 volume_name = 'test-volume'
118
119 volume_properties = {
120 'use_external_resource': False,
121 }
122
123 cinder_client_m = mock.Mock()
124 cinder_client_m.cosmo_delete_resource = mock.Mock()
125
126 ctx_m = self._mock(node_id='a', properties=volume_properties)
127 ctx_m.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = volume_id
128 ctx_m.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] = \
129 volume.VOLUME_OPENSTACK_TYPE
130 ctx_m.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = \
131 volume_name
132
133 volume.delete(cinder_client=cinder_client_m, ctx=ctx_m)
134
135 cinder_client_m.cosmo_delete_resource.assert_called_once_with(
136 volume.VOLUME_OPENSTACK_TYPE, volume_id)
137 self.assertTrue(
138 OPENSTACK_ID_PROPERTY not in ctx_m.instance.runtime_properties)
139 self.assertTrue(OPENSTACK_TYPE_PROPERTY
140 not in ctx_m.instance.runtime_properties)
141 self.assertTrue(OPENSTACK_NAME_PROPERTY
142 not in ctx_m.instance.runtime_properties)
143
144 @mock.patch('openstack_plugin_common.NovaClientWithSugar')
145 @mock.patch('openstack_plugin_common.CinderClientWithSugar')
146 @mock.patch.object(volume, 'wait_until_status', return_value=(None, True))
147 def test_attach(self, wait_until_status_m, cinder_m, nova_m):
148 volume_id = '00000000-0000-0000-0000-000000000000'
149 server_id = '11111111-1111-1111-1111-111111111111'
150 device_name = '/dev/fake'
151
152 volume_ctx = cfy_mocks.MockContext({
153 'node': cfy_mocks.MockContext({
154 'properties': {volume.DEVICE_NAME_PROPERTY: device_name}
155 }),
156 'instance': cfy_mocks.MockContext({
157 'runtime_properties': {
158 OPENSTACK_ID_PROPERTY: volume_id,
159 }
160 })
161 })
162 server_ctx = cfy_mocks.MockContext({
163 'node': cfy_mocks.MockContext({
164 'properties': {}
165 }),
166 'instance': cfy_mocks.MockContext({
167 'runtime_properties': {
168 server.OPENSTACK_ID_PROPERTY: server_id
169 }
170 })
171 })
172
173 ctx_m = self._mock(node_id='a',
174 target=server_ctx,
175 source=volume_ctx)
176
177 nova_instance = nova_m.return_value
178 cinder_instance = cinder_m.return_value
179
180 server.attach_volume(ctx=ctx_m, status_attempts=10,
181 status_timeout=2)
182
183 nova_instance.volumes.create_server_volume.assert_called_once_with(
184 server_id, volume_id, device_name)
185 wait_until_status_m.assert_called_once_with(
186 cinder_client=cinder_instance,
187 volume_id=volume_id,
188 status=volume.VOLUME_STATUS_IN_USE,
189 num_tries=10,
190 timeout=2,
191 )
192
193 @mock.patch('openstack_plugin_common.NovaClientWithSugar')
194 @mock.patch('openstack_plugin_common.CinderClientWithSugar')
195 def _test_cleanup__after_attach_fails(
196 self, expected_err_cls, expect_cleanup,
197 wait_until_status_m, cinder_m, nova_m):
198 volume_id = '00000000-0000-0000-0000-000000000000'
199 server_id = '11111111-1111-1111-1111-111111111111'
200 attachment_id = '22222222-2222-2222-2222-222222222222'
201 device_name = '/dev/fake'
202
203 attachment = {'id': attachment_id,
204 'server_id': server_id,
205 'volume_id': volume_id}
206
207 volume_ctx = cfy_mocks.MockContext({
208 'node': cfy_mocks.MockContext({
209 'properties': {volume.DEVICE_NAME_PROPERTY: device_name}
210 }),
211 'instance': cfy_mocks.MockContext({
212 'runtime_properties': {
213 OPENSTACK_ID_PROPERTY: volume_id,
214 }
215 })
216 })
217 server_ctx = cfy_mocks.MockContext({
218 'node': cfy_mocks.MockContext({
219 'properties': {}
220 }),
221 'instance': cfy_mocks.MockContext({
222 'runtime_properties': {
223 server.OPENSTACK_ID_PROPERTY: server_id
224 }
225 })
226 })
227
228 ctx_m = self._mock(node_id='a',
229 target=server_ctx,
230 source=volume_ctx)
231
232 attached_volume = mock.Mock(id=volume_id,
233 status=volume.VOLUME_STATUS_IN_USE,
234 attachments=[attachment])
235 nova_instance = nova_m.return_value
236 cinder_instance = cinder_m.return_value
237 cinder_instance.volumes.get.return_value = attached_volume
238
239 with self.assertRaises(expected_err_cls):
240 server.attach_volume(ctx=ctx_m, status_attempts=10,
241 status_timeout=2)
242
243 nova_instance.volumes.create_server_volume.assert_called_once_with(
244 server_id, volume_id, device_name)
245 volume.wait_until_status.assert_any_call(
246 cinder_client=cinder_instance,
247 volume_id=volume_id,
248 status=volume.VOLUME_STATUS_IN_USE,
249 num_tries=10,
250 timeout=2,
251 )
252 if expect_cleanup:
253 nova_instance.volumes.delete_server_volume.assert_called_once_with(
254 server_id, attachment_id)
255 self.assertEqual(2, volume.wait_until_status.call_count)
256 volume.wait_until_status.assert_called_with(
257 cinder_client=cinder_instance,
258 volume_id=volume_id,
259 status=volume.VOLUME_STATUS_AVAILABLE,
260 num_tries=10,
261 timeout=2)
262
263 def test_cleanup_after_waituntilstatus_throws_recoverable_error(self):
264 err = cfy_exc.RecoverableError('Some recoverable error')
265 with mock.patch.object(volume, 'wait_until_status',
266 side_effect=[err, (None, True)]) as wait_mock:
267 self._test_cleanup__after_attach_fails(type(err), True, wait_mock)
268
269 def test_cleanup_after_waituntilstatus_throws_any_not_nonrecov_error(self):
270 class ArbitraryNonRecoverableException(Exception):
271 pass
272 err = ArbitraryNonRecoverableException('An exception')
273 with mock.patch.object(volume, 'wait_until_status',
274 side_effect=[err, (None, True)]) as wait_mock:
275 self._test_cleanup__after_attach_fails(type(err), True, wait_mock)
276
277 def test_cleanup_after_waituntilstatus_lets_nonrecov_errors_pass(self):
278 err = cfy_exc.NonRecoverableError('Some non recoverable error')
279 with mock.patch.object(volume, 'wait_until_status',
280 side_effect=[err, (None, True)]) as wait_mock:
281 self._test_cleanup__after_attach_fails(type(err), False, wait_mock)
282
283 @mock.patch.object(volume, 'wait_until_status', return_value=(None, False))
284 def test_cleanup_after_waituntilstatus_times_out(self, wait_mock):
285 self._test_cleanup__after_attach_fails(cfy_exc.RecoverableError, True,
286 wait_mock)
287
288 @mock.patch('openstack_plugin_common.NovaClientWithSugar')
289 @mock.patch('openstack_plugin_common.CinderClientWithSugar')
290 @mock.patch.object(volume, 'wait_until_status', return_value=(None, True))
291 def test_detach(self, wait_until_status_m, cinder_m, nova_m):
292 volume_id = '00000000-0000-0000-0000-000000000000'
293 server_id = '11111111-1111-1111-1111-111111111111'
294 attachment_id = '22222222-2222-2222-2222-222222222222'
295
296 attachment = {'id': attachment_id,
297 'server_id': server_id,
298 'volume_id': volume_id}
299
300 volume_ctx = cfy_mocks.MockContext({
301 'node': cfy_mocks.MockContext({
302 'properties': {}
303 }),
304 'instance': cfy_mocks.MockContext({
305 'runtime_properties': {
306 OPENSTACK_ID_PROPERTY: volume_id,
307 }
308 })
309 })
310 server_ctx = cfy_mocks.MockContext({
311 'node': cfy_mocks.MockContext({
312 'properties': {}
313 }),
314 'instance': cfy_mocks.MockContext({
315 'runtime_properties': {
316 server.OPENSTACK_ID_PROPERTY: server_id
317 }
318 })
319 })
320
321 ctx_m = self._mock(node_id='a',
322 target=server_ctx,
323 source=volume_ctx)
324
325 attached_volume = mock.Mock(id=volume_id,
326 status=volume.VOLUME_STATUS_IN_USE,
327 attachments=[attachment])
328 nova_instance = nova_m.return_value
329 cinder_instance = cinder_m.return_value
330 cinder_instance.volumes.get.return_value = attached_volume
331
332 server.detach_volume(ctx=ctx_m, status_attempts=10, status_timeout=2)
333
334 nova_instance.volumes.delete_server_volume.assert_called_once_with(
335 server_id, attachment_id)
336 volume.wait_until_status.assert_called_once_with(
337 cinder_client=cinder_instance,
338 volume_id=volume_id,
339 status=volume.VOLUME_STATUS_AVAILABLE,
340 num_tries=10,
341 timeout=2,
342 )