Remove tests which do not test anything
[so.git] / aria / multivim-plugin / src / main / python / multivim-plugin / nova_plugin / tests / test_server.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 from os import path
17 import tempfile
18
19 import unittest
20 import mock
21
22 import nova_plugin
23 from cloudify.test_utils import workflow_test
24
25 from openstack_plugin_common import NeutronClientWithSugar, \
26     OPENSTACK_TYPE_PROPERTY, OPENSTACK_ID_PROPERTY
27 from neutron_plugin.network import NETWORK_OPENSTACK_TYPE
28 from neutron_plugin.port import PORT_OPENSTACK_TYPE
29 from nova_plugin.tests.test_relationships import RelationshipsTestBase
30 from nova_plugin.server import _prepare_server_nics
31 from cinder_plugin.volume import VOLUME_OPENSTACK_TYPE
32 from cloudify.exceptions import NonRecoverableError
33 from cloudify.state import current_ctx
34
35 from cloudify.utils import setup_logger
36
37 from cloudify.mocks import (
38     MockNodeContext,
39     MockCloudifyContext,
40     MockNodeInstanceContext,
41     MockRelationshipContext,
42     MockRelationshipSubjectContext
43 )
44
45
46 class TestServer(unittest.TestCase):
47
48     blueprint_path = path.join('resources',
49                                'test-start-operation-retry-blueprint.yaml')
50
51     @mock.patch('nova_plugin.server.create')
52     @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties')
53     @workflow_test(blueprint_path, copy_plugin_yaml=True)
54     def test_nova_server_lifecycle_start(self, cfy_local, *_):
55
56         test_vars = {
57             'counter': 0,
58             'server': mock.MagicMock()
59         }
60
61         def mock_get_server_by_context(*_):
62             s = test_vars['server']
63             if test_vars['counter'] == 0:
64                 s.status = nova_plugin.server.SERVER_STATUS_BUILD
65             else:
66                 s.status = nova_plugin.server.SERVER_STATUS_ACTIVE
67             test_vars['counter'] += 1
68             return s
69
70         with mock.patch('nova_plugin.server.get_server_by_context',
71                         new=mock_get_server_by_context):
72             cfy_local.execute('install', task_retries=3)
73
74         self.assertEqual(2, test_vars['counter'])
75         self.assertEqual(0, test_vars['server'].start.call_count)
76
77     @workflow_test(blueprint_path, copy_plugin_yaml=True)
78     @mock.patch('nova_plugin.server.create')
79     @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties')
80     def test_nova_server_lifecycle_start_after_stop(self, cfy_local, *_):
81
82         test_vars = {
83             'counter': 0,
84             'server': mock.MagicMock()
85         }
86
87         def mock_get_server_by_context(_):
88             s = test_vars['server']
89             if test_vars['counter'] == 0:
90                 s.status = nova_plugin.server.SERVER_STATUS_SHUTOFF
91             elif test_vars['counter'] == 1:
92                 setattr(s,
93                         nova_plugin.server.OS_EXT_STS_TASK_STATE,
94                         nova_plugin.server.SERVER_TASK_STATE_POWERING_ON)
95             else:
96                 s.status = nova_plugin.server.SERVER_STATUS_ACTIVE
97             test_vars['counter'] += 1
98             test_vars['server'] = s
99             return s
100
101         with mock.patch('nova_plugin.server.get_server_by_context',
102                         new=mock_get_server_by_context):
103             cfy_local.execute('install', task_retries=3)
104
105         self.assertEqual(1, test_vars['server'].start.call_count)
106         self.assertEqual(3, test_vars['counter'])
107
108     @workflow_test(blueprint_path, copy_plugin_yaml=True)
109     @mock.patch('nova_plugin.server.create')
110     @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties')
111     def test_nova_server_lifecycle_start_unknown_status(self, cfy_local, *_):
112         test_vars = {
113             'counter': 0,
114             'server': mock.MagicMock()
115         }
116
117         def mock_get_server_by_context(_):
118             s = test_vars['server']
119             if test_vars['counter'] == 0:
120                 s.status = '### unknown-status ###'
121             test_vars['counter'] += 1
122             test_vars['server'] = s
123             return s
124
125         with mock.patch('nova_plugin.server.get_server_by_context',
126                         new=mock_get_server_by_context):
127             self.assertRaisesRegexp(RuntimeError,
128                                     'Unexpected server state',
129                                     cfy_local.execute,
130                                     'install')
131
132         self.assertEqual(0, test_vars['server'].start.call_count)
133         self.assertEqual(1, test_vars['counter'])
134
135     @workflow_test(blueprint_path, copy_plugin_yaml=True)
136     @mock.patch('nova_plugin.server.start')
137     @mock.patch('nova_plugin.server._handle_image_or_flavor')
138     @mock.patch('nova_plugin.server._fail_on_missing_required_parameters')
139     @mock.patch('openstack_plugin_common.nova_client')
140     def test_nova_server_creation_param_integrity(
141             self, cfy_local, mock_nova, *args):
142         cfy_local.execute('install', task_retries=0)
143         calls = mock_nova.Client.return_value.servers.method_calls
144         self.assertEqual(1, len(calls))
145         kws = calls[0][2]
146         self.assertIn('scheduler_hints', kws)
147         self.assertEqual(kws['scheduler_hints'],
148                          {'group': 'affinity-group-id'},
149                          'expecting \'scheduler_hints\' value to exist')
150
151     @workflow_test(blueprint_path, copy_plugin_yaml=True,
152                    inputs={'use_password': True})
153     @mock.patch('nova_plugin.server.create')
154     @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties')
155     @mock.patch(
156         'nova_plugin.server.get_single_connected_node_by_openstack_type',
157         autospec=True, return_value=None)
158     def test_nova_server_with_use_password(self, cfy_local, *_):
159
160         test_vars = {
161             'counter': 0,
162             'server': mock.MagicMock()
163         }
164
165         tmp_path = tempfile.NamedTemporaryFile(prefix='key_name')
166         key_path = tmp_path.name
167
168         def mock_get_server_by_context(_):
169             s = test_vars['server']
170             if test_vars['counter'] == 0:
171                 s.status = nova_plugin.server.SERVER_STATUS_BUILD
172             else:
173                 s.status = nova_plugin.server.SERVER_STATUS_ACTIVE
174             test_vars['counter'] += 1
175
176             def check_agent_key_path(private_key):
177                 self.assertEqual(private_key, key_path)
178                 return private_key
179
180             s.get_password = check_agent_key_path
181             return s
182
183         with mock.patch('nova_plugin.server.get_server_by_context',
184                         mock_get_server_by_context):
185             with mock.patch(
186                     'cloudify.context.BootstrapContext.'
187                     'CloudifyAgent.agent_key_path',
188                     new_callable=mock.PropertyMock, return_value=key_path):
189                 cfy_local.execute('install', task_retries=5)
190
191
192 class TestMergeNICs(unittest.TestCase):
193     def test_merge_prepends_management_network(self):
194         """When the mgmt network isnt in a relationship, its the 1st nic."""
195         mgmt_network_id = 'management network'
196         nics = [{'net-id': 'other network'}]
197
198         merged = nova_plugin.server._merge_nics(mgmt_network_id, nics)
199
200         self.assertEqual(len(merged), 2)
201         self.assertEqual(merged[0]['net-id'], 'management network')
202
203     def test_management_network_in_relationships(self):
204         """When the mgmt network was in a relationship, it's not prepended."""
205         mgmt_network_id = 'management network'
206         nics = [{'net-id': 'other network'}, {'net-id': 'management network'}]
207
208         merged = nova_plugin.server._merge_nics(mgmt_network_id, nics)
209
210         self.assertEqual(nics, merged)
211
212
213 class TestNormalizeNICs(unittest.TestCase):
214     def test_normalize_port_priority(self):
215         """Whe there's both net-id and port-id, port-id is used."""
216         nics = [{'net-id': '1'}, {'port-id': '2'}, {'net-id': 3, 'port-id': 4}]
217         normalized = nova_plugin.server._normalize_nics(nics)
218         expected = [{'net-id': '1'}, {'port-id': '2'}, {'port-id': 4}]
219         self.assertEqual(expected, normalized)
220
221
222 class MockNeutronClient(NeutronClientWithSugar):
223     """A fake neutron client with hard-coded test data."""
224
225     @mock.patch('openstack_plugin_common.OpenStackClient.__init__',
226                 new=mock.Mock())
227     def __init__(self):
228         super(MockNeutronClient, self).__init__()
229
230     @staticmethod
231     def _search_filter(objs, search_params):
232         """Mock neutron's filtering by attributes in list_* methods.
233
234         list_* methods (list_networks, list_ports)
235         """
236         def _matches(obj, search_params):
237             return all(obj[k] == v for k, v in search_params.items())
238         return [obj for obj in objs if _matches(obj, search_params)]
239
240     def list_networks(self, **search_params):
241         networks = [
242             {'name': 'network1', 'id': '1'},
243             {'name': 'network2', 'id': '2'},
244             {'name': 'network3', 'id': '3'},
245             {'name': 'network4', 'id': '4'},
246             {'name': 'network5', 'id': '5'},
247             {'name': 'network6', 'id': '6'},
248             {'name': 'other', 'id': 'other'}
249         ]
250         return {'networks': self._search_filter(networks, search_params)}
251
252     def list_ports(self, **search_params):
253         ports = [
254             {'name': 'port1', 'id': '1', 'network_id': '1'},
255             {'name': 'port2', 'id': '2', 'network_id': '1'},
256             {'name': 'port3', 'id': '3', 'network_id': '2'},
257             {'name': 'port4', 'id': '4', 'network_id': '2'},
258         ]
259         return {'ports': self._search_filter(ports, search_params)}
260
261     def show_port(self, port_id):
262         ports = self.list_ports(id=port_id)
263         return {'port': ports['ports'][0]}
264
265
266 class NICTestBase(RelationshipsTestBase):
267     """Base test class for the NICs tests.
268
269     It comes with helper methods to create a mock cloudify context, with
270     the specified relationships.
271     """
272     mock_neutron = MockNeutronClient()
273
274     def _relationship_spec(self, obj, objtype):
275         return {'node': {'properties': obj},
276                 'instance': {
277                     'runtime_properties': {OPENSTACK_TYPE_PROPERTY: objtype,
278                                            OPENSTACK_ID_PROPERTY: obj['id']}}}
279
280     def _make_vm_ctx_with_ports(self, management_network_name, ports):
281         port_specs = [self._relationship_spec(obj, PORT_OPENSTACK_TYPE)
282                       for obj in ports]
283         vm_properties = {'management_network_name': management_network_name}
284         return self._make_vm_ctx_with_relationships(port_specs,
285                                                     vm_properties)
286
287     def _make_vm_ctx_with_networks(self, management_network_name, networks):
288         network_specs = [self._relationship_spec(obj, NETWORK_OPENSTACK_TYPE)
289                          for obj in networks]
290         vm_properties = {'management_network_name': management_network_name}
291         return self._make_vm_ctx_with_relationships(network_specs,
292                                                     vm_properties)
293
294
295 class TestServerNICs(NICTestBase):
296     """Test preparing the NICs list from server<->network relationships.
297
298     Each test creates a cloudify context that represents a openstack VM
299     with relationships to networks. Then, examine the NICs list produced from
300     the relationships.
301     """
302     def test_nova_server_creation_nics_ordering(self):
303         """NIC list keeps the order of the relationships.
304
305         The nics= list passed to nova.server.create should be ordered
306         depending on the relationships to the networks (as defined in the
307         blueprint).
308         """
309         ctx = self._make_vm_ctx_with_networks(
310             management_network_name='network1',
311             networks=[
312                 {'id': '1'},
313                 {'id': '2'},
314                 {'id': '3'},
315                 {'id': '4'},
316                 {'id': '5'},
317                 {'id': '6'},
318             ])
319         server = {'meta': {}}
320
321         _prepare_server_nics(
322             self.mock_neutron, ctx, server)
323
324         self.assertEqual(
325             ['1', '2', '3', '4', '5', '6'],
326             [n['net-id'] for n in server['nics']])
327
328     def test_server_creation_prepends_mgmt_network(self):
329         """If the management network isn't in a relation, it's the first NIC.
330
331         Creating the server examines the relationships, and if it doesn't find
332         a relationship to the management network, it adds the management
333         network to the NICs list, as the first element.
334         """
335         ctx = self._make_vm_ctx_with_networks(
336             management_network_name='other',
337             networks=[
338                 {'id': '1'},
339                 {'id': '2'},
340                 {'id': '3'},
341                 {'id': '4'},
342                 {'id': '5'},
343                 {'id': '6'},
344             ])
345         server = {'meta': {}}
346
347         _prepare_server_nics(
348             self.mock_neutron, ctx, server)
349
350         first_nic = server['nics'][0]
351         self.assertEqual('other', first_nic['net-id'])
352         self.assertEqual(7, len(server['nics']))
353
354     def test_server_creation_uses_relation_mgmt_nic(self):
355         """If the management network is in a relation, it isn't prepended.
356
357         If the server has a relationship to the management network,
358         a new NIC isn't prepended to the list.
359         """
360         ctx = self._make_vm_ctx_with_networks(
361             management_network_name='network1',
362             networks=[
363                 {'id': '1'},
364                 {'id': '2'},
365                 {'id': '3'},
366                 {'id': '4'},
367                 {'id': '5'},
368                 {'id': '6'},
369             ])
370         server = {'meta': {}}
371
372         _prepare_server_nics(
373             self.mock_neutron, ctx, server)
374         self.assertEqual(6, len(server['nics']))
375
376
377 class TestServerPortNICs(NICTestBase):
378     """Test preparing the NICs list from server<->port relationships.
379
380     Create a cloudify ctx representing a vm with relationships to
381     openstack ports. Then examine the resulting NICs list: check that it
382     contains the networks that the ports were connected to, and that each
383     connection uses the port that was provided.
384     """
385
386     def test_network_with_port(self):
387         """Port on the management network is used to connect to it.
388
389         The NICs list entry for the management network contains the
390         port-id of the port from the relationship, but doesn't contain net-id.
391         """
392         ports = [{'id': '1'}]
393         ctx = self._make_vm_ctx_with_ports('network1', ports)
394         server = {'meta': {}}
395
396         _prepare_server_nics(
397             self.mock_neutron, ctx, server)
398
399         self.assertEqual([{'port-id': '1'}], server['nics'])
400
401     def test_port_not_to_mgmt_network(self):
402         """A NICs list entry is added with the network and the port.
403
404         A relationship to a port must not only add a NIC, but the NIC must
405         also make sure to use that port.
406         """
407         ports = [{'id': '1'}]
408         ctx = self._make_vm_ctx_with_ports('other', ports)
409         server = {'meta': {}}
410
411         _prepare_server_nics(
412             self.mock_neutron, ctx, server)
413         expected = [
414             {'net-id': 'other'},
415             {'port-id': '1'}
416         ]
417         self.assertEqual(expected, server['nics'])
418
419
420 class TestBootFromVolume(unittest.TestCase):
421
422     @mock.patch('nova_plugin.server._get_boot_volume_relationships',
423                 autospec=True)
424     def test_handle_boot_volume(self, mock_get_rels):
425         mock_get_rels.return_value.runtime_properties = {
426                 'external_id': 'test-id',
427                 'availability_zone': 'test-az',
428                 }
429         server = {}
430         ctx = mock.MagicMock()
431         nova_plugin.server._handle_boot_volume(server, ctx)
432         self.assertEqual({'vda': 'test-id:::0'},
433                          server['block_device_mapping'])
434         self.assertEqual('test-az',
435                          server['availability_zone'])
436
437     @mock.patch('nova_plugin.server._get_boot_volume_relationships',
438                 autospec=True, return_value=[])
439     def test_handle_boot_volume_no_boot_volume(self, *_):
440         server = {}
441         ctx = mock.MagicMock()
442         nova_plugin.server._handle_boot_volume(server, ctx)
443         self.assertNotIn('block_device_mapping', server)
444
445
446 class TestImageFromRelationships(unittest.TestCase):
447
448     @mock.patch('glance_plugin.image.'
449                 'get_openstack_ids_of_connected_nodes_by_openstack_type',
450                 autospec=True, return_value=['test-id'])
451     def test_handle_boot_image(self, *_):
452         server = {}
453         ctx = mock.MagicMock()
454         nova_plugin.server.handle_image_from_relationship(server, 'image', ctx)
455         self.assertEqual({'image': 'test-id'}, server)
456
457     @mock.patch('glance_plugin.image.'
458                 'get_openstack_ids_of_connected_nodes_by_openstack_type',
459                 autospec=True, return_value=[])
460     def test_handle_boot_image_no_image(self, *_):
461         server = {}
462         ctx = mock.MagicMock()
463         nova_plugin.server.handle_image_from_relationship(server, 'image', ctx)
464         self.assertNotIn('image', server)
465
466
467 class TestServerRelationships(unittest.TestCase):
468
469     def _get_ctx_mock(self, instance_id, boot):
470         rel_specs = [MockRelationshipContext(
471             target=MockRelationshipSubjectContext(node=MockNodeContext(
472                 properties={'boot': boot}), instance=MockNodeInstanceContext(
473                 runtime_properties={
474                     OPENSTACK_TYPE_PROPERTY: VOLUME_OPENSTACK_TYPE,
475                     OPENSTACK_ID_PROPERTY: instance_id
476                 })))]
477         ctx = mock.MagicMock()
478         ctx.instance = MockNodeInstanceContext(relationships=rel_specs)
479         ctx.logger = setup_logger('mock-logger')
480         return ctx
481
482     def test_boot_volume_relationship(self):
483         instance_id = 'test-id'
484         ctx = self._get_ctx_mock(instance_id, True)
485         result = nova_plugin.server._get_boot_volume_relationships(
486             VOLUME_OPENSTACK_TYPE, ctx)
487         self.assertEqual(
488                 instance_id,
489                 result.runtime_properties['external_id'])
490
491     def test_no_boot_volume_relationship(self):
492         instance_id = 'test-id'
493         ctx = self._get_ctx_mock(instance_id, False)
494         result = nova_plugin.server._get_boot_volume_relationships(
495             VOLUME_OPENSTACK_TYPE, ctx)
496         self.assertFalse(result)
497
498
499 class TestServerNetworkRuntimeProperties(unittest.TestCase):
500
501     @property
502     def mock_ctx(self):
503         return MockCloudifyContext(
504             node_id='test',
505             deployment_id='test',
506             properties={},
507             operation={'retry_number': 0},
508             provider_context={'resources': {}}
509         )
510
511     def test_server_networks_runtime_properties_empty_server(self):
512         ctx = self.mock_ctx
513         current_ctx.set(ctx=ctx)
514         server = mock.MagicMock()
515         setattr(server, 'networks', {})
516         with self.assertRaisesRegexp(
517                 NonRecoverableError,
518                 'The server was created but not attached to a network.'):
519             nova_plugin.server._set_network_and_ip_runtime_properties(server)
520
521     def test_server_networks_runtime_properties_valid_networks(self):
522         ctx = self.mock_ctx
523         current_ctx.set(ctx=ctx)
524         server = mock.MagicMock()
525         network_id = 'management_network'
526         network_ips = ['good', 'bad1', 'bad2']
527         setattr(server,
528                 'networks',
529                 {network_id: network_ips})
530         nova_plugin.server._set_network_and_ip_runtime_properties(server)
531         self.assertIn('networks', ctx.instance.runtime_properties.keys())
532         self.assertIn('ip', ctx.instance.runtime_properties.keys())
533         self.assertEquals(ctx.instance.runtime_properties['ip'], 'good')
534         self.assertEquals(ctx.instance.runtime_properties['networks'],
535                           {network_id: network_ips})
536
537     def test_server_networks_runtime_properties_empty_networks(self):
538         ctx = self.mock_ctx
539         current_ctx.set(ctx=ctx)
540         server = mock.MagicMock()
541         network_id = 'management_network'
542         network_ips = []
543         setattr(server,
544                 'networks',
545                 {network_id: network_ips})
546         nova_plugin.server._set_network_and_ip_runtime_properties(server)
547         self.assertIn('networks', ctx.instance.runtime_properties.keys())
548         self.assertIn('ip', ctx.instance.runtime_properties.keys())
549         self.assertEquals(ctx.instance.runtime_properties['ip'], None)
550         self.assertEquals(ctx.instance.runtime_properties['networks'],
551                           {network_id: network_ips})