Remove tests which do not test anything
[so.git] / aria / multivim-plugin / src / main / python / multivim-plugin / openstack_plugin_common / tests / openstack_client_tests.py
1 ########
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
16 import os
17 import unittest
18 import tempfile
19 import json
20 import __builtin__ as builtins
21
22 import mock
23 from cloudify.exceptions import NonRecoverableError
24
25 from cloudify.mocks import MockCloudifyContext
26 import openstack_plugin_common as common
27
28
29 class ConfigTests(unittest.TestCase):
30
31     @mock.patch.dict('os.environ', clear=True)
32     def test__build_config_from_env_variables_empty(self):
33         cfg = common.Config._build_config_from_env_variables()
34         self.assertEqual({}, cfg)
35
36     @mock.patch.dict('os.environ', clear=True,
37                      OS_AUTH_URL='test_url')
38     def test__build_config_from_env_variables_single(self):
39         cfg = common.Config._build_config_from_env_variables()
40         self.assertEqual({'auth_url': 'test_url'}, cfg)
41
42     @mock.patch.dict('os.environ', clear=True,
43                      OS_AUTH_URL='test_url',
44                      OS_PASSWORD='pass',
45                      OS_REGION_NAME='region')
46     def test__build_config_from_env_variables_multiple(self):
47         cfg = common.Config._build_config_from_env_variables()
48         self.assertEqual({
49             'auth_url': 'test_url',
50             'password': 'pass',
51             'region_name': 'region',
52         }, cfg)
53
54     @mock.patch.dict('os.environ', clear=True,
55                      OS_INVALID='invalid',
56                      PASSWORD='pass',
57                      os_region_name='region')
58     def test__build_config_from_env_variables_all_ignored(self):
59         cfg = common.Config._build_config_from_env_variables()
60         self.assertEqual({}, cfg)
61
62     @mock.patch.dict('os.environ', clear=True,
63                      OS_AUTH_URL='test_url',
64                      OS_PASSWORD='pass',
65                      OS_REGION_NAME='region',
66                      OS_INVALID='invalid',
67                      PASSWORD='pass',
68                      os_region_name='region')
69     def test__build_config_from_env_variables_extract_valid(self):
70         cfg = common.Config._build_config_from_env_variables()
71         self.assertEqual({
72             'auth_url': 'test_url',
73             'password': 'pass',
74             'region_name': 'region',
75         }, cfg)
76
77     def test_update_config_empty_target(self):
78         target = {}
79         override = {'k1': 'u1'}
80         result = override.copy()
81
82         common.Config.update_config(target, override)
83         self.assertEqual(result, target)
84
85     def test_update_config_empty_override(self):
86         target = {'k1': 'v1'}
87         override = {}
88         result = target.copy()
89
90         common.Config.update_config(target, override)
91         self.assertEqual(result, target)
92
93     def test_update_config_disjoint_configs(self):
94         target = {'k1': 'v1'}
95         override = {'k2': 'u2'}
96         result = target.copy()
97         result.update(override)
98
99         common.Config.update_config(target, override)
100         self.assertEqual(result, target)
101
102     def test_update_config_do_not_remove_empty_from_target(self):
103         target = {'k1': ''}
104         override = {}
105         result = target.copy()
106
107         common.Config.update_config(target, override)
108         self.assertEqual(result, target)
109
110     def test_update_config_no_empty_in_override(self):
111         target = {'k1': 'v1', 'k2': 'v2'}
112         override = {'k1': 'u2'}
113         result = target.copy()
114         result.update(override)
115
116         common.Config.update_config(target, override)
117         self.assertEqual(result, target)
118
119     def test_update_config_all_empty_in_override(self):
120         target = {'k1': '', 'k2': 'v2'}
121         override = {'k1': '', 'k3': ''}
122         result = target.copy()
123
124         common.Config.update_config(target, override)
125         self.assertEqual(result, target)
126
127     def test_update_config_misc(self):
128         target = {'k1': 'v1', 'k2': 'v2'}
129         override = {'k1': '', 'k2': 'u2', 'k3': '', 'k4': 'u4'}
130         result = {'k1': 'v1', 'k2': 'u2', 'k4': 'u4'}
131
132         common.Config.update_config(target, override)
133         self.assertEqual(result, target)
134
135     @mock.patch.object(common.Config, 'update_config')
136     @mock.patch.object(common.Config, '_build_config_from_env_variables',
137                        return_value={})
138     @mock.patch.dict('os.environ', clear=True,
139                      values={common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR:
140                              '/this/should/not/exist.json'})
141     def test_get_missing_static_config_missing_file(self, from_env, update):
142         cfg = common.Config.get()
143         self.assertEqual({}, cfg)
144         from_env.assert_called_once_with()
145         update.assert_not_called()
146
147     @mock.patch.object(common.Config, 'update_config')
148     @mock.patch.object(common.Config, '_build_config_from_env_variables',
149                        return_value={})
150     def test_get_empty_static_config_present_file(self, from_env, update):
151         file_cfg = {'k1': 'v1', 'k2': 'v2'}
152         env_var = common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR
153         file = tempfile.NamedTemporaryFile(delete=False)
154         json.dump(file_cfg, file)
155         file.close()
156
157         with mock.patch.dict('os.environ', {env_var: file.name}, clear=True):
158             common.Config.get()
159
160         os.unlink(file.name)
161         from_env.assert_called_once_with()
162         update.assert_called_once_with({}, file_cfg)
163
164     @mock.patch.object(common.Config, 'update_config')
165     @mock.patch.object(common.Config, '_build_config_from_env_variables',
166                        return_value={'k1': 'v1'})
167     def test_get_present_static_config_empty_file(self, from_env, update):
168         file_cfg = {}
169         env_var = common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR
170         file = tempfile.NamedTemporaryFile(delete=False)
171         json.dump(file_cfg, file)
172         file.close()
173
174         with mock.patch.dict('os.environ', {env_var: file.name}, clear=True):
175             common.Config.get()
176
177         os.unlink(file.name)
178         from_env.assert_called_once_with()
179         update.assert_called_once_with({'k1': 'v1'}, file_cfg)
180
181     @mock.patch.object(common.Config, 'update_config')
182     @mock.patch.object(common.Config, '_build_config_from_env_variables',
183                        return_value={'k1': 'v1'})
184     @mock.patch.dict('os.environ', clear=True,
185                      values={common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR:
186                              '/this/should/not/exist.json'})
187     def test_get_present_static_config_missing_file(self, from_env, update):
188         cfg = common.Config.get()
189         self.assertEqual({'k1': 'v1'}, cfg)
190         from_env.assert_called_once_with()
191         update.assert_not_called()
192
193     @mock.patch.object(common.Config, 'update_config')
194     @mock.patch.object(common.Config, '_build_config_from_env_variables',
195                        return_value={'k1': 'v1'})
196     def test_get_all_present(self, from_env, update):
197         file_cfg = {'k2': 'u2'}
198         env_var = common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR
199         file = tempfile.NamedTemporaryFile(delete=False)
200         json.dump(file_cfg, file)
201         file.close()
202
203         with mock.patch.dict('os.environ', {env_var: file.name}, clear=True):
204             common.Config.get()
205
206         os.unlink(file.name)
207         from_env.assert_called_once_with()
208         update.assert_called_once_with({'k1': 'v1'}, file_cfg)
209
210
211 class OpenstackClientTests(unittest.TestCase):
212
213     def test__merge_custom_configuration_no_custom_cfg(self):
214         cfg = {'k1': 'v1'}
215         new = common.OpenStackClient._merge_custom_configuration(cfg, "dummy")
216         self.assertEqual(cfg, new)
217
218     def test__merge_custom_configuration_client_present(self):
219         cfg = {
220             'k1': 'v1',
221             'k2': 'v2',
222             'custom_configuration': {
223                 'dummy': {
224                     'k2': 'u2',
225                     'k3': 'u3'
226                 }
227             }
228         }
229         result = {
230             'k1': 'v1',
231             'k2': 'u2',
232             'k3': 'u3'
233         }
234         bak = cfg.copy()
235         new = common.OpenStackClient._merge_custom_configuration(cfg, "dummy")
236         self.assertEqual(result, new)
237         self.assertEqual(cfg, bak)
238
239     def test__merge_custom_configuration_client_missing(self):
240         cfg = {
241             'k1': 'v1',
242             'k2': 'v2',
243             'custom_configuration': {
244                 'dummy': {
245                     'k2': 'u2',
246                     'k3': 'u3'
247                 }
248             }
249         }
250         result = {
251             'k1': 'v1',
252             'k2': 'v2'
253         }
254         bak = cfg.copy()
255         new = common.OpenStackClient._merge_custom_configuration(cfg, "baddy")
256         self.assertEqual(result, new)
257         self.assertEqual(cfg, bak)
258
259     def test__merge_custom_configuration_multi_client(self):
260         cfg = {
261             'k1': 'v1',
262             'k2': 'v2',
263             'custom_configuration': {
264                 'dummy': {
265                     'k2': 'u2',
266                     'k3': 'u3'
267                 },
268                 'bummy': {
269                     'k1': 'z1'
270                 }
271             }
272         }
273         result = {
274             'k1': 'z1',
275             'k2': 'v2',
276         }
277         bak = cfg.copy()
278         new = common.OpenStackClient._merge_custom_configuration(cfg, "bummy")
279         self.assertEqual(result, new)
280         self.assertEqual(cfg, bak)
281
282     @mock.patch.object(common, 'ctx')
283     def test__merge_custom_configuration_nova_url(self, mock_ctx):
284         cfg = {
285             'nova_url': 'gopher://nova',
286         }
287         bak = cfg.copy()
288
289         self.assertEqual(
290             common.OpenStackClient._merge_custom_configuration(
291                 cfg, 'nova_client'),
292             {'endpoint_override': 'gopher://nova'},
293         )
294         self.assertEqual(
295             common.OpenStackClient._merge_custom_configuration(
296                 cfg, 'dummy'),
297             {},
298         )
299         self.assertEqual(cfg, bak)
300         mock_ctx.logger.warn.assert_has_calls([
301             mock.call(
302                 "'nova_url' property is deprecated. Use `custom_configuration."
303                 "nova_client.endpoint_override` instead."),
304             mock.call(
305                 "'nova_url' property is deprecated. Use `custom_configuration."
306                 "nova_client.endpoint_override` instead."),
307         ])
308
309     @mock.patch('keystoneauth1.session.Session')
310     def test___init___multi_region(self, m_session):
311         mock_client_class = mock.MagicMock()
312
313         cfg = {
314             'auth_url': 'test-auth_url/v3',
315             'region': 'test-region',
316         }
317
318         with mock.patch.object(
319             builtins, 'open',
320             mock.mock_open(
321                 read_data="""
322                 {
323                     "region": "region from file",
324                     "other": "this one should get through"
325                 }
326                 """
327             ),
328             create=True,
329         ):
330             common.OpenStackClient('fred', mock_client_class, cfg)
331
332         mock_client_class.assert_called_once_with(
333             region_name='test-region',
334             other='this one should get through',
335             session=m_session.return_value,
336             )
337
338     def test__validate_auth_params_missing(self):
339         with self.assertRaises(NonRecoverableError):
340             common.OpenStackClient._validate_auth_params({})
341
342     def test__validate_auth_params_too_much(self):
343         with self.assertRaises(NonRecoverableError):
344             common.OpenStackClient._validate_auth_params({
345                 'auth_url': 'url',
346                 'password': 'pass',
347                 'username': 'user',
348                 'tenant_name': 'tenant',
349                 'project_id': 'project_test',
350             })
351
352     def test__validate_auth_params_v2(self):
353         common.OpenStackClient._validate_auth_params({
354             'auth_url': 'url',
355             'password': 'pass',
356             'username': 'user',
357             'tenant_name': 'tenant',
358         })
359
360     def test__validate_auth_params_v3(self):
361         common.OpenStackClient._validate_auth_params({
362             'auth_url': 'url',
363             'password': 'pass',
364             'username': 'user',
365             'project_id': 'project_test',
366             'user_domain_name': 'user_domain',
367         })
368
369     def test__validate_auth_params_v3_mod(self):
370         common.OpenStackClient._validate_auth_params({
371             'auth_url': 'url',
372             'password': 'pass',
373             'username': 'user',
374             'user_domain_name': 'user_domain',
375             'project_name': 'project_test_name',
376             'project_domain_name': 'project_domain',
377         })
378
379     def test__validate_auth_params_skip_insecure(self):
380         common.OpenStackClient._validate_auth_params({
381             'auth_url': 'url',
382             'password': 'pass',
383             'username': 'user',
384             'user_domain_name': 'user_domain',
385             'project_name': 'project_test_name',
386             'project_domain_name': 'project_domain',
387             'insecure': True
388         })
389
390     def test__split_config(self):
391         auth = {'auth_url': 'url', 'password': 'pass'}
392         misc = {'misc1': 'val1', 'misc2': 'val2'}
393         all = dict(auth)
394         all.update(misc)
395
396         a, m = common.OpenStackClient._split_config(all)
397
398         self.assertEqual(auth, a)
399         self.assertEqual(misc, m)
400
401     @mock.patch.object(common, 'loading')
402     @mock.patch.object(common, 'session')
403     def test__authenticate_secure(self, mock_session, mock_loading):
404         auth_params = {'k1': 'v1'}
405         common.OpenStackClient._authenticate(auth_params)
406         loader = mock_loading.get_plugin_loader.return_value
407         loader.load_from_options.assert_called_once_with(k1='v1')
408         auth = loader.load_from_options.return_value
409         mock_session.Session.assert_called_once_with(auth=auth, verify=True)
410
411     @mock.patch.object(common, 'loading')
412     @mock.patch.object(common, 'session')
413     def test__authenticate_secure_explicit(self, mock_session, mock_loading):
414         auth_params = {'k1': 'v1', 'insecure': False}
415         common.OpenStackClient._authenticate(auth_params)
416         loader = mock_loading.get_plugin_loader.return_value
417         loader.load_from_options.assert_called_once_with(k1='v1')
418         auth = loader.load_from_options.return_value
419         mock_session.Session.assert_called_once_with(auth=auth, verify=True)
420
421     @mock.patch.object(common, 'loading')
422     @mock.patch.object(common, 'session')
423     def test__authenticate_insecure(self, mock_session, mock_loading):
424         auth_params = {'k1': 'v1', 'insecure': True}
425         common.OpenStackClient._authenticate(auth_params)
426         loader = mock_loading.get_plugin_loader.return_value
427         loader.load_from_options.assert_called_once_with(k1='v1')
428         auth = loader.load_from_options.return_value
429         mock_session.Session.assert_called_once_with(auth=auth, verify=False)
430
431     @mock.patch.object(common, 'loading')
432     @mock.patch.object(common, 'session')
433     def test__authenticate_secure_misc(self, mock_session, mock_loading):
434         params = {'k1': 'v1'}
435         tests = ('', 'a', [], {}, set(), 4, 0, -1, 3.14, 0.0, None)
436         for test in tests:
437             auth_params = params.copy()
438             auth_params['insecure'] = test
439
440             common.OpenStackClient._authenticate(auth_params)
441             loader = mock_loading.get_plugin_loader.return_value
442             loader.load_from_options.assert_called_with(**params)
443             auth = loader.load_from_options.return_value
444             mock_session.Session.assert_called_with(auth=auth, verify=True)
445
446     @mock.patch.object(common, 'cinder_client')
447     def test_cinder_client_get_name_from_resource(self, cc_mock):
448         ccws = common.CinderClientWithSugar()
449         mock_volume = mock.Mock()
450
451         self.assertIs(
452             mock_volume.name,
453             ccws.get_name_from_resource(mock_volume))
454
455
456 class ClientsConfigTest(unittest.TestCase):
457
458     def setUp(self):
459         file = tempfile.NamedTemporaryFile(delete=False)
460         json.dump(self.get_file_cfg(), file)
461         file.close()
462         self.addCleanup(os.unlink, file.name)
463
464         env_cfg = self.get_env_cfg()
465         env_cfg[common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR] = file.name
466         mock.patch.dict('os.environ', env_cfg, clear=True).start()
467
468         self.loading = mock.patch.object(common, 'loading').start()
469         self.session = mock.patch.object(common, 'session').start()
470         self.nova = mock.patch.object(common, 'nova_client').start()
471         self.neutron = mock.patch.object(common, 'neutron_client').start()
472         self.cinder = mock.patch.object(common, 'cinder_client').start()
473         self.addCleanup(mock.patch.stopall)
474
475         self.loader = self.loading.get_plugin_loader.return_value
476         self.auth = self.loader.load_from_options.return_value
477
478
479 class CustomConfigFromInputs(ClientsConfigTest):
480
481     def get_file_cfg(self):
482         return {
483             'username': 'file-username',
484             'password': 'file-password',
485             'tenant_name': 'file-tenant-name',
486             'custom_configuration': {
487                 'nova_client': {
488                     'username': 'custom-username',
489                     'password': 'custom-password',
490                     'tenant_name': 'custom-tenant-name'
491                 },
492             }
493         }
494
495     def get_inputs_cfg(self):
496         return {
497             'auth_url': 'envar-auth-url',
498             'username': 'inputs-username',
499             'custom_configuration': {
500                 'neutron_client': {
501                     'password': 'inputs-custom-password'
502                 },
503                 'cinder_client': {
504                     'password': 'inputs-custom-password',
505                     'auth_url': 'inputs-custom-auth-url',
506                     'extra_key': 'extra-value'
507                 },
508             }
509         }
510
511     def get_env_cfg(self):
512         return {
513             'OS_USERNAME': 'envar-username',
514             'OS_PASSWORD': 'envar-password',
515             'OS_TENANT_NAME': 'envar-tenant-name',
516             'OS_AUTH_URL': 'envar-auth-url',
517             common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR: file.name
518         }
519
520     def test_nova(self):
521         common.NovaClientWithSugar(config=self.get_inputs_cfg())
522         self.loader.load_from_options.assert_called_once_with(
523             username='inputs-username',
524             password='file-password',
525             tenant_name='file-tenant-name',
526             auth_url='envar-auth-url'
527         )
528         self.session.Session.assert_called_with(auth=self.auth, verify=True)
529         self.nova.Client.assert_called_once_with(
530             '2', session=self.session.Session.return_value)
531
532     def test_neutron(self):
533         common.NeutronClientWithSugar(config=self.get_inputs_cfg())
534         self.loader.load_from_options.assert_called_once_with(
535             username='inputs-username',
536             password='inputs-custom-password',
537             tenant_name='file-tenant-name',
538             auth_url='envar-auth-url'
539         )
540         self.session.Session.assert_called_with(auth=self.auth, verify=True)
541         self.neutron.Client.assert_called_once_with(
542             session=self.session.Session.return_value)
543
544     def test_cinder(self):
545         common.CinderClientWithSugar(config=self.get_inputs_cfg())
546         self.loader.load_from_options.assert_called_once_with(
547             username='inputs-username',
548             password='inputs-custom-password',
549             tenant_name='file-tenant-name',
550             auth_url='inputs-custom-auth-url'
551         )
552         self.session.Session.assert_called_with(auth=self.auth, verify=True)
553         self.cinder.Client.assert_called_once_with(
554             '2', session=self.session.Session.return_value,
555             extra_key='extra-value')
556
557
558 class CustomConfigFromFile(ClientsConfigTest):
559
560     def get_file_cfg(self):
561         return {
562             'username': 'file-username',
563             'password': 'file-password',
564             'tenant_name': 'file-tenant-name',
565             'custom_configuration': {
566                 'nova_client': {
567                     'username': 'custom-username',
568                     'password': 'custom-password',
569                     'tenant_name': 'custom-tenant-name'
570                 },
571             }
572         }
573
574     def get_inputs_cfg(self):
575         return {
576             'auth_url': 'envar-auth-url',
577             'username': 'inputs-username',
578         }
579
580     def get_env_cfg(self):
581         return {
582             'OS_USERNAME': 'envar-username',
583             'OS_PASSWORD': 'envar-password',
584             'OS_TENANT_NAME': 'envar-tenant-name',
585             'OS_AUTH_URL': 'envar-auth-url',
586             common.Config.OPENSTACK_CONFIG_PATH_ENV_VAR: file.name
587         }
588
589     def test_nova(self):
590         common.NovaClientWithSugar(config=self.get_inputs_cfg())
591         self.loader.load_from_options.assert_called_once_with(
592             username='custom-username',
593             password='custom-password',
594             tenant_name='custom-tenant-name',
595             auth_url='envar-auth-url'
596         )
597         self.session.Session.assert_called_with(auth=self.auth, verify=True)
598         self.nova.Client.assert_called_once_with(
599             '2', session=self.session.Session.return_value)
600
601     def test_neutron(self):
602         common.NeutronClientWithSugar(config=self.get_inputs_cfg())
603         self.loader.load_from_options.assert_called_once_with(
604             username='inputs-username',
605             password='file-password',
606             tenant_name='file-tenant-name',
607             auth_url='envar-auth-url'
608         )
609         self.session.Session.assert_called_with(auth=self.auth, verify=True)
610         self.neutron.Client.assert_called_once_with(
611             session=self.session.Session.return_value)
612
613     def test_cinder(self):
614         common.CinderClientWithSugar(config=self.get_inputs_cfg())
615         self.loader.load_from_options.assert_called_once_with(
616             username='inputs-username',
617             password='file-password',
618             tenant_name='file-tenant-name',
619             auth_url='envar-auth-url'
620         )
621         self.session.Session.assert_called_with(auth=self.auth, verify=True)
622         self.cinder.Client.assert_called_once_with(
623             '2', session=self.session.Session.return_value)
624
625
626 class PutClientInKwTests(unittest.TestCase):
627
628     def test_override_prop_empty_ctx(self):
629         props = {}
630         ctx = MockCloudifyContext(node_id='a20846', properties=props)
631         kwargs = {
632             'ctx': ctx,
633             'openstack_config': {
634                 'p1': 'v1'
635             }
636         }
637         expected_cfg = kwargs['openstack_config']
638
639         client_class = mock.MagicMock()
640         common._put_client_in_kw('mock_client', client_class, kwargs)
641         client_class.assert_called_once_with(config=expected_cfg)
642
643     def test_override_prop_nonempty_ctx(self):
644         props = {
645             'openstack_config': {
646                 'p1': 'u1',
647                 'p2': 'u2'
648             }
649         }
650         props_copy = props.copy()
651         ctx = MockCloudifyContext(node_id='a20846', properties=props)
652         kwargs = {
653             'ctx': ctx,
654             'openstack_config': {
655                 'p1': 'v1',
656                 'p3': 'v3'
657             }
658         }
659         expected_cfg = {
660             'p1': 'v1',
661             'p2': 'u2',
662             'p3': 'v3'
663         }
664
665         client_class = mock.MagicMock()
666         common._put_client_in_kw('mock_client', client_class, kwargs)
667         client_class.assert_called_once_with(config=expected_cfg)
668         # Making sure that _put_client_in_kw will not modify
669         # 'openstack_config' property of a node.
670         self.assertEqual(props_copy, ctx.node.properties)
671
672     def test_override_runtime_prop(self):
673         props = {
674             'openstack_config': {
675                 'p1': 'u1',
676                 'p2': 'u2'
677             }
678         }
679         runtime_props = {
680             'openstack_config': {
681                 'p1': 'u3'
682             }
683         }
684         props_copy = props.copy()
685         runtime_props_copy = runtime_props.copy()
686         ctx = MockCloudifyContext(node_id='a20847', properties=props,
687                                   runtime_properties=runtime_props)
688         kwargs = {
689             'ctx': ctx
690         }
691         expected_cfg = {
692             'p1': 'u3',
693             'p2': 'u2'
694         }
695         client_class = mock.MagicMock()
696         common._put_client_in_kw('mock_client', client_class, kwargs)
697         client_class.assert_called_once_with(config=expected_cfg)
698         self.assertEqual(props_copy, ctx.node.properties)
699         self.assertEqual(runtime_props_copy, ctx.instance.runtime_properties)
700
701
702 class ResourceQuotaTests(unittest.TestCase):
703
704     def _test_quota_validation(self, amount, quota, failure_expected):
705         ctx = MockCloudifyContext(node_id='node_id', properties={})
706         client = mock.MagicMock()
707
708         def mock_cosmo_list(_):
709             return [x for x in range(0, amount)]
710         client.cosmo_list = mock_cosmo_list
711
712         def mock_get_quota(_):
713             return quota
714         client.get_quota = mock_get_quota
715
716         if failure_expected:
717             self.assertRaisesRegexp(
718                 NonRecoverableError,
719                 'cannot be created due to quota limitations',
720                 common.validate_resource,
721                 ctx=ctx, sugared_client=client,
722                 openstack_type='openstack_type')
723         else:
724             common.validate_resource(
725                 ctx=ctx, sugared_client=client,
726                 openstack_type='openstack_type')
727
728     def test_equals_quotas(self):
729         self._test_quota_validation(3, 3, True)
730
731     def test_exceeded_quota(self):
732         self._test_quota_validation(5, 3, True)
733
734     def test_infinite_quota(self):
735         self._test_quota_validation(5, -1, False)
736
737
738 class UseExternalResourceTests(unittest.TestCase):
739
740     def _test_use_external_resource(self,
741                                     is_external,
742                                     create_if_missing,
743                                     exists):
744         properties = {'create_if_missing': create_if_missing,
745                       'use_external_resource': is_external,
746                       'resource_id': 'resource_id'}
747         client_mock = mock.MagicMock()
748         os_type = 'test'
749
750         def _raise_error(*_):
751             raise NonRecoverableError('Error')
752
753         def _return_something(*_):
754             return mock.MagicMock()
755
756         return_value = _return_something if exists else _raise_error
757         if exists:
758             properties.update({'resource_id': 'rid'})
759
760         node_context = MockCloudifyContext(node_id='a20847',
761                                            properties=properties)
762         with mock.patch(
763                 'openstack_plugin_common._get_resource_by_name_or_id_from_ctx',
764                 new=return_value):
765             return common.use_external_resource(node_context,
766                                                 client_mock, os_type)
767
768     def test_use_existing_resource(self):
769         self.assertIsNotNone(self._test_use_external_resource(True, True,
770                                                               True))
771         self.assertIsNotNone(self._test_use_external_resource(True, False,
772                                                               True))
773
774     def test_create_resource(self):
775         self.assertIsNone(self._test_use_external_resource(False, True, False))
776         self.assertIsNone(self._test_use_external_resource(False, False,
777                                                            False))
778         self.assertIsNone(self._test_use_external_resource(True, True, False))
779
780     def test_raise_error(self):
781         # If exists and shouldn't it is checked in resource
782         # validation so below scenario is not tested here
783         self.assertRaises(NonRecoverableError,
784                           self._test_use_external_resource,
785                           is_external=True,
786                           create_if_missing=False,
787                           exists=False)
788
789
790 class ValidateResourceTests(unittest.TestCase):
791
792     def _test_validate_resource(self,
793                                 is_external,
794                                 create_if_missing,
795                                 exists,
796                                 client_mock_provided=None):
797         properties = {'create_if_missing': create_if_missing,
798                       'use_external_resource': is_external,
799                       'resource_id': 'resource_id'}
800         client_mock = client_mock_provided or mock.MagicMock()
801         os_type = 'test'
802
803         def _raise_error(*_):
804             raise NonRecoverableError('Error')
805
806         def _return_something(*_):
807             return mock.MagicMock()
808         return_value = _return_something if exists else _raise_error
809         if exists:
810             properties.update({'resource_id': 'rid'})
811
812         node_context = MockCloudifyContext(node_id='a20847',
813                                            properties=properties)
814         with mock.patch(
815                 'openstack_plugin_common._get_resource_by_name_or_id_from_ctx',
816                 new=return_value):
817             return common.validate_resource(node_context, client_mock, os_type)
818
819     def test_use_existing_resource(self):
820         self._test_validate_resource(True, True, True)
821         self._test_validate_resource(True, False, True)
822
823     def test_create_resource(self):
824         client_mock = mock.MagicMock()
825         client_mock.cosmo_list.return_value = ['a', 'b', 'c']
826         client_mock.get_quota.return_value = 5
827         self._test_validate_resource(False, True, False, client_mock)
828         self._test_validate_resource(False, False, False, client_mock)
829         self._test_validate_resource(True, True, False, client_mock)
830
831     def test_raise_error(self):
832         # If exists and shouldn't it is checked in resource
833         # validation so below scenario is not tested here
834         self.assertRaises(NonRecoverableError,
835                           self._test_validate_resource,
836                           is_external=True,
837                           create_if_missing=False,
838                           exists=False)
839
840     def test_raise_quota_error(self):
841         client_mock = mock.MagicMock()
842         client_mock.cosmo_list.return_value = ['a', 'b', 'c']
843         client_mock.get_quota.return_value = 3
844         self.assertRaises(NonRecoverableError,
845                           self._test_validate_resource,
846                           is_external=True,
847                           create_if_missing=True,
848                           exists=False,
849                           client_mock_provided=client_mock)