2 # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
23 from cloudify.test_utils import workflow_test
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
35 from cloudify.utils import setup_logger
37 from cloudify.mocks import (
40 MockNodeInstanceContext,
41 MockRelationshipContext,
42 MockRelationshipSubjectContext
46 class TestServer(unittest.TestCase):
48 blueprint_path = path.join('resources',
49 'test-start-operation-retry-blueprint.yaml')
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, *_):
58 'server': mock.MagicMock()
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
66 s.status = nova_plugin.server.SERVER_STATUS_ACTIVE
67 test_vars['counter'] += 1
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)
74 self.assertEqual(2, test_vars['counter'])
75 self.assertEqual(0, test_vars['server'].start.call_count)
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, *_):
84 'server': mock.MagicMock()
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:
93 nova_plugin.server.OS_EXT_STS_TASK_STATE,
94 nova_plugin.server.SERVER_TASK_STATE_POWERING_ON)
96 s.status = nova_plugin.server.SERVER_STATUS_ACTIVE
97 test_vars['counter'] += 1
98 test_vars['server'] = s
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)
105 self.assertEqual(1, test_vars['server'].start.call_count)
106 self.assertEqual(3, test_vars['counter'])
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, *_):
114 'server': mock.MagicMock()
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
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',
132 self.assertEqual(0, test_vars['server'].start.call_count)
133 self.assertEqual(1, test_vars['counter'])
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))
146 self.assertIn('scheduler_hints', kws)
147 self.assertEqual(kws['scheduler_hints'],
148 {'group': 'affinity-group-id'},
149 'expecting \'scheduler_hints\' value to exist')
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')
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, *_):
162 'server': mock.MagicMock()
165 tmp_path = tempfile.NamedTemporaryFile(prefix='key_name')
166 key_path = tmp_path.name
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
173 s.status = nova_plugin.server.SERVER_STATUS_ACTIVE
174 test_vars['counter'] += 1
176 def check_agent_key_path(private_key):
177 self.assertEqual(private_key, key_path)
180 s.get_password = check_agent_key_path
183 with mock.patch('nova_plugin.server.get_server_by_context',
184 mock_get_server_by_context):
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)
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'}]
198 merged = nova_plugin.server._merge_nics(mgmt_network_id, nics)
200 self.assertEqual(len(merged), 2)
201 self.assertEqual(merged[0]['net-id'], 'management network')
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'}]
208 merged = nova_plugin.server._merge_nics(mgmt_network_id, nics)
210 self.assertEqual(nics, merged)
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)
222 class MockNeutronClient(NeutronClientWithSugar):
223 """A fake neutron client with hard-coded test data."""
225 @mock.patch('openstack_plugin_common.OpenStackClient.__init__',
228 super(MockNeutronClient, self).__init__()
231 def _search_filter(objs, search_params):
232 """Mock neutron's filtering by attributes in list_* methods.
234 list_* methods (list_networks, list_ports)
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)]
240 def list_networks(self, **search_params):
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'}
250 return {'networks': self._search_filter(networks, search_params)}
252 def list_ports(self, **search_params):
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'},
259 return {'ports': self._search_filter(ports, search_params)}
261 def show_port(self, port_id):
262 ports = self.list_ports(id=port_id)
263 return {'port': ports['ports'][0]}
266 class NICTestBase(RelationshipsTestBase):
267 """Base test class for the NICs tests.
269 It comes with helper methods to create a mock cloudify context, with
270 the specified relationships.
272 mock_neutron = MockNeutronClient()
274 def _relationship_spec(self, obj, objtype):
275 return {'node': {'properties': obj},
277 'runtime_properties': {OPENSTACK_TYPE_PROPERTY: objtype,
278 OPENSTACK_ID_PROPERTY: obj['id']}}}
280 def _make_vm_ctx_with_ports(self, management_network_name, ports):
281 port_specs = [self._relationship_spec(obj, PORT_OPENSTACK_TYPE)
283 vm_properties = {'management_network_name': management_network_name}
284 return self._make_vm_ctx_with_relationships(port_specs,
287 def _make_vm_ctx_with_networks(self, management_network_name, networks):
288 network_specs = [self._relationship_spec(obj, NETWORK_OPENSTACK_TYPE)
290 vm_properties = {'management_network_name': management_network_name}
291 return self._make_vm_ctx_with_relationships(network_specs,
295 class TestServerNICs(NICTestBase):
296 """Test preparing the NICs list from server<->network relationships.
298 Each test creates a cloudify context that represents a openstack VM
299 with relationships to networks. Then, examine the NICs list produced from
302 def test_nova_server_creation_nics_ordering(self):
303 """NIC list keeps the order of the relationships.
305 The nics= list passed to nova.server.create should be ordered
306 depending on the relationships to the networks (as defined in the
309 ctx = self._make_vm_ctx_with_networks(
310 management_network_name='network1',
319 server = {'meta': {}}
321 _prepare_server_nics(
322 self.mock_neutron, ctx, server)
325 ['1', '2', '3', '4', '5', '6'],
326 [n['net-id'] for n in server['nics']])
328 def test_server_creation_prepends_mgmt_network(self):
329 """If the management network isn't in a relation, it's the first NIC.
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.
335 ctx = self._make_vm_ctx_with_networks(
336 management_network_name='other',
345 server = {'meta': {}}
347 _prepare_server_nics(
348 self.mock_neutron, ctx, server)
350 first_nic = server['nics'][0]
351 self.assertEqual('other', first_nic['net-id'])
352 self.assertEqual(7, len(server['nics']))
354 def test_server_creation_uses_relation_mgmt_nic(self):
355 """If the management network is in a relation, it isn't prepended.
357 If the server has a relationship to the management network,
358 a new NIC isn't prepended to the list.
360 ctx = self._make_vm_ctx_with_networks(
361 management_network_name='network1',
370 server = {'meta': {}}
372 _prepare_server_nics(
373 self.mock_neutron, ctx, server)
374 self.assertEqual(6, len(server['nics']))
377 class TestServerPortNICs(NICTestBase):
378 """Test preparing the NICs list from server<->port relationships.
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.
386 def test_network_with_port(self):
387 """Port on the management network is used to connect to it.
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.
392 ports = [{'id': '1'}]
393 ctx = self._make_vm_ctx_with_ports('network1', ports)
394 server = {'meta': {}}
396 _prepare_server_nics(
397 self.mock_neutron, ctx, server)
399 self.assertEqual([{'port-id': '1'}], server['nics'])
401 def test_port_not_to_mgmt_network(self):
402 """A NICs list entry is added with the network and the port.
404 A relationship to a port must not only add a NIC, but the NIC must
405 also make sure to use that port.
407 ports = [{'id': '1'}]
408 ctx = self._make_vm_ctx_with_ports('other', ports)
409 server = {'meta': {}}
411 _prepare_server_nics(
412 self.mock_neutron, ctx, server)
417 self.assertEqual(expected, server['nics'])
420 class TestBootFromVolume(unittest.TestCase):
422 @mock.patch('nova_plugin.server._get_boot_volume_relationships',
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',
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'])
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, *_):
441 ctx = mock.MagicMock()
442 nova_plugin.server._handle_boot_volume(server, ctx)
443 self.assertNotIn('block_device_mapping', server)
446 class TestImageFromRelationships(unittest.TestCase):
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, *_):
453 ctx = mock.MagicMock()
454 nova_plugin.server.handle_image_from_relationship(server, 'image', ctx)
455 self.assertEqual({'image': 'test-id'}, server)
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, *_):
462 ctx = mock.MagicMock()
463 nova_plugin.server.handle_image_from_relationship(server, 'image', ctx)
464 self.assertNotIn('image', server)
467 class TestServerRelationships(unittest.TestCase):
469 def _get_ctx_mock(self, instance_id, boot):
470 rel_specs = [MockRelationshipContext(
471 target=MockRelationshipSubjectContext(node=MockNodeContext(
472 properties={'boot': boot}), instance=MockNodeInstanceContext(
474 OPENSTACK_TYPE_PROPERTY: VOLUME_OPENSTACK_TYPE,
475 OPENSTACK_ID_PROPERTY: instance_id
477 ctx = mock.MagicMock()
478 ctx.instance = MockNodeInstanceContext(relationships=rel_specs)
479 ctx.logger = setup_logger('mock-logger')
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)
489 result.runtime_properties['external_id'])
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)
499 class TestServerNetworkRuntimeProperties(unittest.TestCase):
503 return MockCloudifyContext(
505 deployment_id='test',
507 operation={'retry_number': 0},
508 provider_context={'resources': {}}
511 def test_server_networks_runtime_properties_empty_server(self):
513 current_ctx.set(ctx=ctx)
514 server = mock.MagicMock()
515 setattr(server, 'networks', {})
516 with self.assertRaisesRegexp(
518 'The server was created but not attached to a network.'):
519 nova_plugin.server._set_network_and_ip_runtime_properties(server)
521 def test_server_networks_runtime_properties_valid_networks(self):
523 current_ctx.set(ctx=ctx)
524 server = mock.MagicMock()
525 network_id = 'management_network'
526 network_ips = ['good', 'bad1', 'bad2']
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})
537 def test_server_networks_runtime_properties_empty_networks(self):
539 current_ctx.set(ctx=ctx)
540 server = mock.MagicMock()
541 network_id = 'management_network'
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})