diff options
author | dfilppi <dewayne@gigaspaces.com> | 2017-08-07 20:10:53 +0000 |
---|---|---|
committer | dfilppi <dewayne@gigaspaces.com> | 2017-08-07 20:10:53 +0000 |
commit | 9981f55920a6f1c1f20396d42e35b075b22f6a8f (patch) | |
tree | 1199993b9bae728c5274ae3062988dc9f357eb5b /aria/multivim-plugin/nova_plugin/tests | |
parent | 4538e26e2a60bd325d63c19bcc7d0fed37ccce96 (diff) |
ARIA multivim plugin initial checkin
Change-Id: I3a24ab6fc5ba54466bfecaf596a13b8907248ae8
Issue-id: SO-77
Signed-off-by: DeWayne Filppi <dewayne@gigaspaces.com>
Diffstat (limited to 'aria/multivim-plugin/nova_plugin/tests')
9 files changed, 1349 insertions, 0 deletions
diff --git a/aria/multivim-plugin/nova_plugin/tests/__init__.py b/aria/multivim-plugin/nova_plugin/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/__init__.py diff --git a/aria/multivim-plugin/nova_plugin/tests/resources/test-keypair-validation-blueprint.yaml b/aria/multivim-plugin/nova_plugin/tests/resources/test-keypair-validation-blueprint.yaml new file mode 100644 index 0000000000..22b7fb5362 --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/resources/test-keypair-validation-blueprint.yaml @@ -0,0 +1,23 @@ +tosca_definitions_version: cloudify_dsl_1_3 + +imports: + - https://raw.githubusercontent.com/cloudify-cosmo/cloudify-manager/4.1/resources/rest-service/cloudify/types/types.yaml + - plugin.yaml + +inputs: + private_key: {} + is_keypair_external: {} + + +node_templates: + + keypair: + type: cloudify.openstack.nodes.KeyPair + properties: + private_key_path: { get_input: private_key } + use_external_resource: { get_input: is_keypair_external } + openstack_config: + username: aaa + password: aaa + tenant_name: aaa + auth_url: aaa diff --git a/aria/multivim-plugin/nova_plugin/tests/resources/test-server-create-secgroup.yaml b/aria/multivim-plugin/nova_plugin/tests/resources/test-server-create-secgroup.yaml new file mode 100644 index 0000000000..70b75f6bf5 --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/resources/test-server-create-secgroup.yaml @@ -0,0 +1,31 @@ +tosca_definitions_version: cloudify_dsl_1_3 + +imports: + - https://raw.githubusercontent.com/cloudify-cosmo/cloudify-manager/4.1/resources/rest-service/cloudify/types/types.yaml + - plugin.yaml + +inputs: + use_password: + type: boolean + default: false + +node_templates: + + security_group: + type: cloudify.openstack.nodes.SecurityGroup + + server: + type: cloudify.openstack.nodes.Server + properties: + install_agent: false + use_password: { get_input: use_password } + openstack_config: + username: aaa + password: aaa + tenant_name: aaa + auth_url: aaa + server: + key_name: 'aa' + relationships: + - type: cloudify.openstack.server_connected_to_security_group + target: security_group diff --git a/aria/multivim-plugin/nova_plugin/tests/resources/test-start-operation-retry-blueprint.yaml b/aria/multivim-plugin/nova_plugin/tests/resources/test-start-operation-retry-blueprint.yaml new file mode 100644 index 0000000000..275806cf5a --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/resources/test-start-operation-retry-blueprint.yaml @@ -0,0 +1,31 @@ +tosca_definitions_version: cloudify_dsl_1_3 + +imports: + - https://raw.githubusercontent.com/cloudify-cosmo/cloudify-manager/4.1/resources/rest-service/cloudify/types/types.yaml + - plugin.yaml + +inputs: + use_password: + type: boolean + default: false + +node_templates: + server: + type: cloudify.openstack.nodes.Server + properties: + install_agent: false + use_password: { get_input: use_password } + server: + key_name: key + scheduler_hints: + group: affinity-group-id + openstack_config: + username: aaa + password: aaa + tenant_name: aaa + auth_url: aaa + interfaces: + cloudify.interfaces.lifecycle: + start: + inputs: + start_retry_interval: 1 diff --git a/aria/multivim-plugin/nova_plugin/tests/test_relationships.py b/aria/multivim-plugin/nova_plugin/tests/test_relationships.py new file mode 100644 index 0000000000..2814057fb7 --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/test_relationships.py @@ -0,0 +1,228 @@ +######### +# Copyright (c) 2016 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. + +"""Test the functions related to retrieving relationship information. + +Functions under test are mostly inside openstack_plugin_common: +get_relationships_by_openstack_type +get_connected_nodes_by_openstack_type +get_openstack_ids_of_connected_nodes_by_openstack_type +get_single_connected_node_by_openstack_type +""" + +import uuid +from unittest import TestCase + +from neutron_plugin.network import NETWORK_OPENSTACK_TYPE + +from cloudify.exceptions import NonRecoverableError + +from cloudify.mocks import ( + MockCloudifyContext, + MockNodeContext, + MockNodeInstanceContext, + MockRelationshipContext, + MockRelationshipSubjectContext, +) +from openstack_plugin_common import ( + OPENSTACK_ID_PROPERTY, + OPENSTACK_TYPE_PROPERTY, + get_openstack_id_of_single_connected_node_by_openstack_type, + get_openstack_ids_of_connected_nodes_by_openstack_type, + get_relationships_by_openstack_type, + get_single_connected_node_by_openstack_type, +) + + +class RelationshipsTestBase(TestCase): + def _make_vm_ctx_with_relationships(self, rel_specs, properties=None): + """Prepare a mock CloudifyContext from the given relationship spec. + + rel_specs is an ordered collection of relationship specs - dicts + with the keys "node" and "instance" used to construct the + MockNodeContext and the MockNodeInstanceContext, and optionally a + "type" key. + Examples: [ + {}, + {"node": {"id": 5}}, + { + "type": "some_type", + "instance": { + "id": 3, + "runtime_properties":{} + } + } + ] + """ + if properties is None: + properties = {} + relationships = [] + for rel_spec in rel_specs: + node = rel_spec.get('node', {}) + node_id = node.pop('id', uuid.uuid4().hex) + + instance = rel_spec.get('instance', {}) + instance_id = instance.pop('id', '{0}_{1}'.format( + node_id, uuid.uuid4().hex)) + if 'properties' not in node: + node['properties'] = {} + node_ctx = MockNodeContext(id=node_id, **node) + instance_ctx = MockNodeInstanceContext(id=instance_id, **instance) + + rel_subject_ctx = MockRelationshipSubjectContext( + node=node_ctx, instance=instance_ctx) + rel_type = rel_spec.get('type') + rel_ctx = MockRelationshipContext(target=rel_subject_ctx, + type=rel_type) + relationships.append(rel_ctx) + return MockCloudifyContext(node_id='vm', properties=properties, + relationships=relationships) + + +class TestGettingRelatedResources(RelationshipsTestBase): + + def test_get_relationships_finds_all_by_type(self): + """get_relationships_by_openstack_type returns all rels that match.""" + rel_specs = [{ + 'instance': { + 'id': instance_id, + 'runtime_properties': { + OPENSTACK_TYPE_PROPERTY: NETWORK_OPENSTACK_TYPE + } + } + } for instance_id in range(3)] + + rel_specs.append({ + 'instance': { + 'runtime_properties': { + OPENSTACK_TYPE_PROPERTY: 'something else' + } + } + }) + + ctx = self._make_vm_ctx_with_relationships(rel_specs) + filtered = get_relationships_by_openstack_type(ctx, + NETWORK_OPENSTACK_TYPE) + self.assertEqual(3, len(filtered)) + + def test_get_ids_of_nodes_by_type(self): + + rel_spec = { + 'instance': { + 'runtime_properties': { + OPENSTACK_TYPE_PROPERTY: NETWORK_OPENSTACK_TYPE, + OPENSTACK_ID_PROPERTY: 'the node id' + } + } + } + ctx = self._make_vm_ctx_with_relationships([rel_spec]) + ids = get_openstack_ids_of_connected_nodes_by_openstack_type( + ctx, NETWORK_OPENSTACK_TYPE) + self.assertEqual(['the node id'], ids) + + +class TestGetSingleByID(RelationshipsTestBase): + def _make_instances(self, ids): + """Mock a context with relationships to instances with given ids.""" + rel_specs = [{ + 'node': { + 'id': node_id + }, + 'instance': { + 'runtime_properties': { + OPENSTACK_TYPE_PROPERTY: NETWORK_OPENSTACK_TYPE, + OPENSTACK_ID_PROPERTY: node_id + } + } + } for node_id in ids] + return self._make_vm_ctx_with_relationships(rel_specs) + + def test_get_single_id(self): + ctx = self._make_instances(['the node id']) + found_id = get_openstack_id_of_single_connected_node_by_openstack_type( + ctx, NETWORK_OPENSTACK_TYPE) + self.assertEqual('the node id', found_id) + + def test_get_single_id_two_found(self): + ctx = self._make_instances([0, 1]) + self.assertRaises( + NonRecoverableError, + get_openstack_id_of_single_connected_node_by_openstack_type, ctx, + NETWORK_OPENSTACK_TYPE) + + def test_get_single_id_two_found_if_exists_true(self): + ctx = self._make_instances([0, 1]) + + try: + get_openstack_id_of_single_connected_node_by_openstack_type( + ctx, NETWORK_OPENSTACK_TYPE, if_exists=True) + except NonRecoverableError as e: + self.assertIn(NETWORK_OPENSTACK_TYPE, e.message) + else: + self.fail() + + def test_get_single_id_if_exists_none_found(self): + ctx = self._make_instances([]) + found = get_openstack_id_of_single_connected_node_by_openstack_type( + ctx, NETWORK_OPENSTACK_TYPE, if_exists=True) + self.assertIsNone(found) + + def test_get_single_id_none_found(self): + rel_spec = [] + ctx = self._make_vm_ctx_with_relationships(rel_spec) + self.assertRaises( + NonRecoverableError, + get_openstack_id_of_single_connected_node_by_openstack_type, + ctx, + NETWORK_OPENSTACK_TYPE) + + def test_get_single_node(self): + ctx = self._make_instances(['the node id']) + found_node = get_single_connected_node_by_openstack_type( + ctx, NETWORK_OPENSTACK_TYPE) + self.assertEqual('the node id', found_node.id) + + def test_get_single_node_two_found(self): + ctx = self._make_instances([0, 1]) + self.assertRaises( + NonRecoverableError, + get_single_connected_node_by_openstack_type, + ctx, NETWORK_OPENSTACK_TYPE) + + def test_get_single_node_two_found_if_exists(self): + ctx = self._make_instances([0, 1]) + + self.assertRaises( + NonRecoverableError, + get_single_connected_node_by_openstack_type, + ctx, + NETWORK_OPENSTACK_TYPE, + if_exists=True) + + def test_get_single_node_if_exists_none_found(self): + ctx = self._make_instances([]) + + found = get_single_connected_node_by_openstack_type( + ctx, NETWORK_OPENSTACK_TYPE, if_exists=True) + self.assertIsNone(found) + + def test_get_single_node_none_found(self): + ctx = self._make_instances([]) + + self.assertRaises( + NonRecoverableError, + get_single_connected_node_by_openstack_type, + ctx, + NETWORK_OPENSTACK_TYPE) diff --git a/aria/multivim-plugin/nova_plugin/tests/test_server.py b/aria/multivim-plugin/nova_plugin/tests/test_server.py new file mode 100644 index 0000000000..a50930555c --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/test_server.py @@ -0,0 +1,551 @@ +######### +# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. + +from os import path +import tempfile + +import unittest +import mock + +import nova_plugin +from cloudify.test_utils import workflow_test + +from openstack_plugin_common import NeutronClientWithSugar, \ + OPENSTACK_TYPE_PROPERTY, OPENSTACK_ID_PROPERTY +from neutron_plugin.network import NETWORK_OPENSTACK_TYPE +from neutron_plugin.port import PORT_OPENSTACK_TYPE +from nova_plugin.tests.test_relationships import RelationshipsTestBase +from nova_plugin.server import _prepare_server_nics +from cinder_plugin.volume import VOLUME_OPENSTACK_TYPE +from cloudify.exceptions import NonRecoverableError +from cloudify.state import current_ctx + +from cloudify.utils import setup_logger + +from cloudify.mocks import ( + MockNodeContext, + MockCloudifyContext, + MockNodeInstanceContext, + MockRelationshipContext, + MockRelationshipSubjectContext +) + + +class TestServer(unittest.TestCase): + + blueprint_path = path.join('resources', + 'test-start-operation-retry-blueprint.yaml') + + @mock.patch('nova_plugin.server.create') + @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties') + @workflow_test(blueprint_path, copy_plugin_yaml=True) + def test_nova_server_lifecycle_start(self, cfy_local, *_): + + test_vars = { + 'counter': 0, + 'server': mock.MagicMock() + } + + def mock_get_server_by_context(*_): + s = test_vars['server'] + if test_vars['counter'] == 0: + s.status = nova_plugin.server.SERVER_STATUS_BUILD + else: + s.status = nova_plugin.server.SERVER_STATUS_ACTIVE + test_vars['counter'] += 1 + return s + + with mock.patch('nova_plugin.server.get_server_by_context', + new=mock_get_server_by_context): + cfy_local.execute('install', task_retries=3) + + self.assertEqual(2, test_vars['counter']) + self.assertEqual(0, test_vars['server'].start.call_count) + + @workflow_test(blueprint_path, copy_plugin_yaml=True) + @mock.patch('nova_plugin.server.create') + @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties') + def test_nova_server_lifecycle_start_after_stop(self, cfy_local, *_): + + test_vars = { + 'counter': 0, + 'server': mock.MagicMock() + } + + def mock_get_server_by_context(_): + s = test_vars['server'] + if test_vars['counter'] == 0: + s.status = nova_plugin.server.SERVER_STATUS_SHUTOFF + elif test_vars['counter'] == 1: + setattr(s, + nova_plugin.server.OS_EXT_STS_TASK_STATE, + nova_plugin.server.SERVER_TASK_STATE_POWERING_ON) + else: + s.status = nova_plugin.server.SERVER_STATUS_ACTIVE + test_vars['counter'] += 1 + test_vars['server'] = s + return s + + with mock.patch('nova_plugin.server.get_server_by_context', + new=mock_get_server_by_context): + cfy_local.execute('install', task_retries=3) + + self.assertEqual(1, test_vars['server'].start.call_count) + self.assertEqual(3, test_vars['counter']) + + @workflow_test(blueprint_path, copy_plugin_yaml=True) + @mock.patch('nova_plugin.server.create') + @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties') + def test_nova_server_lifecycle_start_unknown_status(self, cfy_local, *_): + test_vars = { + 'counter': 0, + 'server': mock.MagicMock() + } + + def mock_get_server_by_context(_): + s = test_vars['server'] + if test_vars['counter'] == 0: + s.status = '### unknown-status ###' + test_vars['counter'] += 1 + test_vars['server'] = s + return s + + with mock.patch('nova_plugin.server.get_server_by_context', + new=mock_get_server_by_context): + self.assertRaisesRegexp(RuntimeError, + 'Unexpected server state', + cfy_local.execute, + 'install') + + self.assertEqual(0, test_vars['server'].start.call_count) + self.assertEqual(1, test_vars['counter']) + + @workflow_test(blueprint_path, copy_plugin_yaml=True) + @mock.patch('nova_plugin.server.start') + @mock.patch('nova_plugin.server._handle_image_or_flavor') + @mock.patch('nova_plugin.server._fail_on_missing_required_parameters') + @mock.patch('openstack_plugin_common.nova_client') + def test_nova_server_creation_param_integrity( + self, cfy_local, mock_nova, *args): + cfy_local.execute('install', task_retries=0) + calls = mock_nova.Client.return_value.servers.method_calls + self.assertEqual(1, len(calls)) + kws = calls[0][2] + self.assertIn('scheduler_hints', kws) + self.assertEqual(kws['scheduler_hints'], + {'group': 'affinity-group-id'}, + 'expecting \'scheduler_hints\' value to exist') + + @workflow_test(blueprint_path, copy_plugin_yaml=True, + inputs={'use_password': True}) + @mock.patch('nova_plugin.server.create') + @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties') + @mock.patch( + 'nova_plugin.server.get_single_connected_node_by_openstack_type', + autospec=True, return_value=None) + def test_nova_server_with_use_password(self, cfy_local, *_): + + test_vars = { + 'counter': 0, + 'server': mock.MagicMock() + } + + tmp_path = tempfile.NamedTemporaryFile(prefix='key_name') + key_path = tmp_path.name + + def mock_get_server_by_context(_): + s = test_vars['server'] + if test_vars['counter'] == 0: + s.status = nova_plugin.server.SERVER_STATUS_BUILD + else: + s.status = nova_plugin.server.SERVER_STATUS_ACTIVE + test_vars['counter'] += 1 + + def check_agent_key_path(private_key): + self.assertEqual(private_key, key_path) + return private_key + + s.get_password = check_agent_key_path + return s + + with mock.patch('nova_plugin.server.get_server_by_context', + mock_get_server_by_context): + with mock.patch( + 'cloudify.context.BootstrapContext.' + 'CloudifyAgent.agent_key_path', + new_callable=mock.PropertyMock, return_value=key_path): + cfy_local.execute('install', task_retries=5) + + +class TestMergeNICs(unittest.TestCase): + def test_merge_prepends_management_network(self): + """When the mgmt network isnt in a relationship, its the 1st nic.""" + mgmt_network_id = 'management network' + nics = [{'net-id': 'other network'}] + + merged = nova_plugin.server._merge_nics(mgmt_network_id, nics) + + self.assertEqual(len(merged), 2) + self.assertEqual(merged[0]['net-id'], 'management network') + + def test_management_network_in_relationships(self): + """When the mgmt network was in a relationship, it's not prepended.""" + mgmt_network_id = 'management network' + nics = [{'net-id': 'other network'}, {'net-id': 'management network'}] + + merged = nova_plugin.server._merge_nics(mgmt_network_id, nics) + + self.assertEqual(nics, merged) + + +class TestNormalizeNICs(unittest.TestCase): + def test_normalize_port_priority(self): + """Whe there's both net-id and port-id, port-id is used.""" + nics = [{'net-id': '1'}, {'port-id': '2'}, {'net-id': 3, 'port-id': 4}] + normalized = nova_plugin.server._normalize_nics(nics) + expected = [{'net-id': '1'}, {'port-id': '2'}, {'port-id': 4}] + self.assertEqual(expected, normalized) + + +class MockNeutronClient(NeutronClientWithSugar): + """A fake neutron client with hard-coded test data.""" + + @mock.patch('openstack_plugin_common.OpenStackClient.__init__', + new=mock.Mock()) + def __init__(self): + super(MockNeutronClient, self).__init__() + + @staticmethod + def _search_filter(objs, search_params): + """Mock neutron's filtering by attributes in list_* methods. + + list_* methods (list_networks, list_ports) + """ + def _matches(obj, search_params): + return all(obj[k] == v for k, v in search_params.items()) + return [obj for obj in objs if _matches(obj, search_params)] + + def list_networks(self, **search_params): + networks = [ + {'name': 'network1', 'id': '1'}, + {'name': 'network2', 'id': '2'}, + {'name': 'network3', 'id': '3'}, + {'name': 'network4', 'id': '4'}, + {'name': 'network5', 'id': '5'}, + {'name': 'network6', 'id': '6'}, + {'name': 'other', 'id': 'other'} + ] + return {'networks': self._search_filter(networks, search_params)} + + def list_ports(self, **search_params): + ports = [ + {'name': 'port1', 'id': '1', 'network_id': '1'}, + {'name': 'port2', 'id': '2', 'network_id': '1'}, + {'name': 'port3', 'id': '3', 'network_id': '2'}, + {'name': 'port4', 'id': '4', 'network_id': '2'}, + ] + return {'ports': self._search_filter(ports, search_params)} + + def show_port(self, port_id): + ports = self.list_ports(id=port_id) + return {'port': ports['ports'][0]} + + +class NICTestBase(RelationshipsTestBase): + """Base test class for the NICs tests. + + It comes with helper methods to create a mock cloudify context, with + the specified relationships. + """ + mock_neutron = MockNeutronClient() + + def _relationship_spec(self, obj, objtype): + return {'node': {'properties': obj}, + 'instance': { + 'runtime_properties': {OPENSTACK_TYPE_PROPERTY: objtype, + OPENSTACK_ID_PROPERTY: obj['id']}}} + + def _make_vm_ctx_with_ports(self, management_network_name, ports): + port_specs = [self._relationship_spec(obj, PORT_OPENSTACK_TYPE) + for obj in ports] + vm_properties = {'management_network_name': management_network_name} + return self._make_vm_ctx_with_relationships(port_specs, + vm_properties) + + def _make_vm_ctx_with_networks(self, management_network_name, networks): + network_specs = [self._relationship_spec(obj, NETWORK_OPENSTACK_TYPE) + for obj in networks] + vm_properties = {'management_network_name': management_network_name} + return self._make_vm_ctx_with_relationships(network_specs, + vm_properties) + + +class TestServerNICs(NICTestBase): + """Test preparing the NICs list from server<->network relationships. + + Each test creates a cloudify context that represents a openstack VM + with relationships to networks. Then, examine the NICs list produced from + the relationships. + """ + def test_nova_server_creation_nics_ordering(self): + """NIC list keeps the order of the relationships. + + The nics= list passed to nova.server.create should be ordered + depending on the relationships to the networks (as defined in the + blueprint). + """ + ctx = self._make_vm_ctx_with_networks( + management_network_name='network1', + networks=[ + {'id': '1'}, + {'id': '2'}, + {'id': '3'}, + {'id': '4'}, + {'id': '5'}, + {'id': '6'}, + ]) + server = {'meta': {}} + + _prepare_server_nics( + self.mock_neutron, ctx, server) + + self.assertEqual( + ['1', '2', '3', '4', '5', '6'], + [n['net-id'] for n in server['nics']]) + + def test_server_creation_prepends_mgmt_network(self): + """If the management network isn't in a relation, it's the first NIC. + + Creating the server examines the relationships, and if it doesn't find + a relationship to the management network, it adds the management + network to the NICs list, as the first element. + """ + ctx = self._make_vm_ctx_with_networks( + management_network_name='other', + networks=[ + {'id': '1'}, + {'id': '2'}, + {'id': '3'}, + {'id': '4'}, + {'id': '5'}, + {'id': '6'}, + ]) + server = {'meta': {}} + + _prepare_server_nics( + self.mock_neutron, ctx, server) + + first_nic = server['nics'][0] + self.assertEqual('other', first_nic['net-id']) + self.assertEqual(7, len(server['nics'])) + + def test_server_creation_uses_relation_mgmt_nic(self): + """If the management network is in a relation, it isn't prepended. + + If the server has a relationship to the management network, + a new NIC isn't prepended to the list. + """ + ctx = self._make_vm_ctx_with_networks( + management_network_name='network1', + networks=[ + {'id': '1'}, + {'id': '2'}, + {'id': '3'}, + {'id': '4'}, + {'id': '5'}, + {'id': '6'}, + ]) + server = {'meta': {}} + + _prepare_server_nics( + self.mock_neutron, ctx, server) + self.assertEqual(6, len(server['nics'])) + + +class TestServerPortNICs(NICTestBase): + """Test preparing the NICs list from server<->port relationships. + + Create a cloudify ctx representing a vm with relationships to + openstack ports. Then examine the resulting NICs list: check that it + contains the networks that the ports were connected to, and that each + connection uses the port that was provided. + """ + + def test_network_with_port(self): + """Port on the management network is used to connect to it. + + The NICs list entry for the management network contains the + port-id of the port from the relationship, but doesn't contain net-id. + """ + ports = [{'id': '1'}] + ctx = self._make_vm_ctx_with_ports('network1', ports) + server = {'meta': {}} + + _prepare_server_nics( + self.mock_neutron, ctx, server) + + self.assertEqual([{'port-id': '1'}], server['nics']) + + def test_port_not_to_mgmt_network(self): + """A NICs list entry is added with the network and the port. + + A relationship to a port must not only add a NIC, but the NIC must + also make sure to use that port. + """ + ports = [{'id': '1'}] + ctx = self._make_vm_ctx_with_ports('other', ports) + server = {'meta': {}} + + _prepare_server_nics( + self.mock_neutron, ctx, server) + expected = [ + {'net-id': 'other'}, + {'port-id': '1'} + ] + self.assertEqual(expected, server['nics']) + + +class TestBootFromVolume(unittest.TestCase): + + @mock.patch('nova_plugin.server._get_boot_volume_relationships', + autospec=True) + def test_handle_boot_volume(self, mock_get_rels): + mock_get_rels.return_value.runtime_properties = { + 'external_id': 'test-id', + 'availability_zone': 'test-az', + } + server = {} + ctx = mock.MagicMock() + nova_plugin.server._handle_boot_volume(server, ctx) + self.assertEqual({'vda': 'test-id:::0'}, + server['block_device_mapping']) + self.assertEqual('test-az', + server['availability_zone']) + + @mock.patch('nova_plugin.server._get_boot_volume_relationships', + autospec=True, return_value=[]) + def test_handle_boot_volume_no_boot_volume(self, *_): + server = {} + ctx = mock.MagicMock() + nova_plugin.server._handle_boot_volume(server, ctx) + self.assertNotIn('block_device_mapping', server) + + +class TestImageFromRelationships(unittest.TestCase): + + @mock.patch('glance_plugin.image.' + 'get_openstack_ids_of_connected_nodes_by_openstack_type', + autospec=True, return_value=['test-id']) + def test_handle_boot_image(self, *_): + server = {} + ctx = mock.MagicMock() + nova_plugin.server.handle_image_from_relationship(server, 'image', ctx) + self.assertEqual({'image': 'test-id'}, server) + + @mock.patch('glance_plugin.image.' + 'get_openstack_ids_of_connected_nodes_by_openstack_type', + autospec=True, return_value=[]) + def test_handle_boot_image_no_image(self, *_): + server = {} + ctx = mock.MagicMock() + nova_plugin.server.handle_image_from_relationship(server, 'image', ctx) + self.assertNotIn('image', server) + + +class TestServerRelationships(unittest.TestCase): + + def _get_ctx_mock(self, instance_id, boot): + rel_specs = [MockRelationshipContext( + target=MockRelationshipSubjectContext(node=MockNodeContext( + properties={'boot': boot}), instance=MockNodeInstanceContext( + runtime_properties={ + OPENSTACK_TYPE_PROPERTY: VOLUME_OPENSTACK_TYPE, + OPENSTACK_ID_PROPERTY: instance_id + })))] + ctx = mock.MagicMock() + ctx.instance = MockNodeInstanceContext(relationships=rel_specs) + ctx.logger = setup_logger('mock-logger') + return ctx + + def test_boot_volume_relationship(self): + instance_id = 'test-id' + ctx = self._get_ctx_mock(instance_id, True) + result = nova_plugin.server._get_boot_volume_relationships( + VOLUME_OPENSTACK_TYPE, ctx) + self.assertEqual( + instance_id, + result.runtime_properties['external_id']) + + def test_no_boot_volume_relationship(self): + instance_id = 'test-id' + ctx = self._get_ctx_mock(instance_id, False) + result = nova_plugin.server._get_boot_volume_relationships( + VOLUME_OPENSTACK_TYPE, ctx) + self.assertFalse(result) + + +class TestServerNetworkRuntimeProperties(unittest.TestCase): + + @property + def mock_ctx(self): + return MockCloudifyContext( + node_id='test', + deployment_id='test', + properties={}, + operation={'retry_number': 0}, + provider_context={'resources': {}} + ) + + def test_server_networks_runtime_properties_empty_server(self): + ctx = self.mock_ctx + current_ctx.set(ctx=ctx) + server = mock.MagicMock() + setattr(server, 'networks', {}) + with self.assertRaisesRegexp( + NonRecoverableError, + 'The server was created but not attached to a network.'): + nova_plugin.server._set_network_and_ip_runtime_properties(server) + + def test_server_networks_runtime_properties_valid_networks(self): + ctx = self.mock_ctx + current_ctx.set(ctx=ctx) + server = mock.MagicMock() + network_id = 'management_network' + network_ips = ['good', 'bad1', 'bad2'] + setattr(server, + 'networks', + {network_id: network_ips}) + nova_plugin.server._set_network_and_ip_runtime_properties(server) + self.assertIn('networks', ctx.instance.runtime_properties.keys()) + self.assertIn('ip', ctx.instance.runtime_properties.keys()) + self.assertEquals(ctx.instance.runtime_properties['ip'], 'good') + self.assertEquals(ctx.instance.runtime_properties['networks'], + {network_id: network_ips}) + + def test_server_networks_runtime_properties_empty_networks(self): + ctx = self.mock_ctx + current_ctx.set(ctx=ctx) + server = mock.MagicMock() + network_id = 'management_network' + network_ips = [] + setattr(server, + 'networks', + {network_id: network_ips}) + nova_plugin.server._set_network_and_ip_runtime_properties(server) + self.assertIn('networks', ctx.instance.runtime_properties.keys()) + self.assertIn('ip', ctx.instance.runtime_properties.keys()) + self.assertEquals(ctx.instance.runtime_properties['ip'], None) + self.assertEquals(ctx.instance.runtime_properties['networks'], + {network_id: network_ips}) diff --git a/aria/multivim-plugin/nova_plugin/tests/test_server_image_and_flavor.py b/aria/multivim-plugin/nova_plugin/tests/test_server_image_and_flavor.py new file mode 100644 index 0000000000..2ae475843c --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/test_server_image_and_flavor.py @@ -0,0 +1,228 @@ +######### +# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. + + +import unittest + +import mock +from novaclient import exceptions as nova_exceptions + +import nova_plugin.server as server +from cloudify.exceptions import NonRecoverableError +from cloudify.mocks import MockCloudifyContext + + +class TestServerImageAndFlavor(unittest.TestCase): + + def test_no_image_and_no_flavor(self): + node_props = { + 'image': '', + 'flavor': '' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + self.assertRaises(NonRecoverableError, + server._handle_image_or_flavor, + serv, nova_client, 'image') + self.assertRaises(NonRecoverableError, + server._handle_image_or_flavor, + serv, nova_client, 'flavor') + + def test_image_and_flavor_properties_as_names(self): + node_props = { + 'image': 'some-image-name', + 'flavor': 'some-flavor-name' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + server._handle_image_or_flavor(serv, nova_client, 'image') + server._handle_image_or_flavor(serv, nova_client, 'flavor') + + self.assertEquals('some-image-id', serv.get('image')) + self.assertEquals('some-flavor-id', serv.get('flavor')) + + def test_image_and_flavor_properties_as_ids(self): + node_props = { + 'image': 'some-image-id', + 'flavor': 'some-flavor-id' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + server._handle_image_or_flavor(serv, nova_client, 'image') + server._handle_image_or_flavor(serv, nova_client, 'flavor') + + self.assertEquals('some-image-id', serv.get('image')) + self.assertEquals('some-flavor-id', serv.get('flavor')) + + def test_image_id_and_flavor_id(self): + node_props = { + 'image': '', + 'flavor': '' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + serv['image'] = 'some-image-id' + serv['flavor'] = 'some-flavor-id' + server._handle_image_or_flavor(serv, nova_client, 'image') + server._handle_image_or_flavor(serv, nova_client, 'flavor') + + self.assertEquals('some-image-id', serv.get('image')) + self.assertEquals('some-flavor-id', serv.get('flavor')) + + def test_image_name_and_flavor_name(self): + node_props = { + 'image': '', + 'flavor': '' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + serv['image_name'] = 'some-image-name' + serv['flavor_name'] = 'some-flavor-name' + server._handle_image_or_flavor(serv, nova_client, 'image') + server._handle_image_or_flavor(serv, nova_client, 'flavor') + + self.assertEquals('some-image-id', serv.get('image')) + self.assertNotIn('image_name', serv) + self.assertEquals('some-flavor-id', serv.get('flavor')) + self.assertNotIn('flavor_name', serv) + + def test_unknown_image_name_and_flavor_name(self): + node_props = { + 'image': '', + 'flavor': '' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + serv['image_name'] = 'some-unknown-image-name' + serv['flavor_name'] = 'some-unknown-flavor-name' + + self.assertRaises(nova_exceptions.NotFound, + server._handle_image_or_flavor, + serv, nova_client, 'image') + self.assertRaises(nova_exceptions.NotFound, + server._handle_image_or_flavor, + serv, nova_client, 'flavor') + + def test_image_id_and_flavor_id_override_on_properties(self): + node_props = { + 'image': 'properties-image-id', + 'flavor': 'properties-flavor-id' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + serv['image'] = 'some-image-id' + serv['flavor'] = 'some-flavor-id' + server._handle_image_or_flavor(serv, nova_client, 'image') + server._handle_image_or_flavor(serv, nova_client, 'flavor') + + self.assertEquals('some-image-id', serv.get('image')) + self.assertEquals('some-flavor-id', serv.get('flavor')) + + def test_image_name_and_flavor_name_override_on_properties(self): + node_props = { + 'image': 'properties-image-id', + 'flavor': 'properties-flavor-id' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + serv['image_name'] = 'some-image-name' + serv['flavor_name'] = 'some-flavor-name' + server._handle_image_or_flavor(serv, nova_client, 'image') + server._handle_image_or_flavor(serv, nova_client, 'flavor') + + self.assertEquals('some-image-id', serv.get('image')) + self.assertNotIn('image_name', serv) + self.assertEquals('some-flavor-id', serv.get('flavor')) + self.assertNotIn('flavor_name', serv) + + def test_image_name_and_flavor_name_override_on_image_and_flavor_ids(self): + node_props = { + 'image': '', + 'flavor': '' + } + with mock.patch('nova_plugin.server.ctx', + self._get_mock_ctx_with_node_properties(node_props)): + nova_client = self._get_mocked_nova_client() + + serv = {} + serv['image'] = 'some-bad-image-id' + serv['image_name'] = 'some-image-name' + serv['flavor'] = 'some-bad-flavor-id' + serv['flavor_name'] = 'some-flavor-name' + server._handle_image_or_flavor(serv, nova_client, 'image') + server._handle_image_or_flavor(serv, nova_client, 'flavor') + + self.assertEquals('some-image-id', serv.get('image')) + self.assertNotIn('image_name', serv) + self.assertEquals('some-flavor-id', serv.get('flavor')) + self.assertNotIn('flavor_name', serv) + + @staticmethod + def _get_mocked_nova_client(): + nova_client = mock.MagicMock() + + def mock_get_if_exists(prop_name, **kwargs): + is_image = prop_name == 'image' + searched_name = kwargs.get('name') + if (is_image and searched_name == 'some-image-name') or \ + (not is_image and searched_name == 'some-flavor-name'): + result = mock.MagicMock() + result.id = 'some-image-id' if \ + is_image else 'some-flavor-id' + return result + return [] + + def mock_find_generator(prop_name): + def mock_find(**kwargs): + result = mock_get_if_exists(prop_name, **kwargs) + if not result: + raise nova_exceptions.NotFound(404) + return result + return mock_find + + nova_client.cosmo_plural = lambda x: '{0}s'.format(x) + nova_client.cosmo_get_if_exists = mock_get_if_exists + nova_client.images.find = mock_find_generator('image') + nova_client.flavors.find = mock_find_generator('flavor') + return nova_client + + @staticmethod + def _get_mock_ctx_with_node_properties(properties): + return MockCloudifyContext(node_id='test_node_id', + properties=properties) diff --git a/aria/multivim-plugin/nova_plugin/tests/test_userdata.py b/aria/multivim-plugin/nova_plugin/tests/test_userdata.py new file mode 100644 index 0000000000..d7f056d72c --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/test_userdata.py @@ -0,0 +1,63 @@ +######### +# Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. + +import unittest + +import mock + +from cloudify.mocks import MockCloudifyContext + +from nova_plugin import userdata + + +def ctx_mock(): + result = MockCloudifyContext( + node_id='d', + properties={}) + result.node.type_hierarchy = ['cloudify.nodes.Compute'] + return result + + +class TestServerUserdataHandling(unittest.TestCase): + + @mock.patch('nova_plugin.userdata.ctx', ctx_mock()) + def test_no_userdata(self): + server_conf = {} + userdata.handle_userdata(server_conf) + self.assertEqual(server_conf, {}) + + def test_agent_installation_userdata(self): + ctx = ctx_mock() + ctx.agent.init_script = lambda: 'SCRIPT' + with mock.patch('nova_plugin.userdata.ctx', ctx): + server_conf = {} + userdata.handle_userdata(server_conf) + self.assertEqual(server_conf, {'userdata': 'SCRIPT'}) + + @mock.patch('nova_plugin.userdata.ctx', ctx_mock()) + def test_existing_userdata(self): + server_conf = {'userdata': 'EXISTING'} + server_conf_copy = server_conf.copy() + userdata.handle_userdata(server_conf) + self.assertEqual(server_conf, server_conf_copy) + + def test_existing_and_agent_installation_userdata(self): + ctx = ctx_mock() + ctx.agent.init_script = lambda: '#! SCRIPT' + with mock.patch('nova_plugin.userdata.ctx', ctx): + server_conf = {'userdata': '#! EXISTING'} + userdata.handle_userdata(server_conf) + self.assertTrue(server_conf['userdata'].startswith( + 'Content-Type: multi')) diff --git a/aria/multivim-plugin/nova_plugin/tests/test_validation.py b/aria/multivim-plugin/nova_plugin/tests/test_validation.py new file mode 100644 index 0000000000..aa1dfdd814 --- /dev/null +++ b/aria/multivim-plugin/nova_plugin/tests/test_validation.py @@ -0,0 +1,194 @@ +######### +# Copyright (c) 2016 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. + +import os +from os import path +import tempfile +import shutil + +import unittest +import mock + +from cloudify.test_utils import workflow_test +from nova_plugin.keypair import creation_validation +from cloudify.exceptions import NonRecoverableError + +PRIVATE_KEY_NAME = 'private_key' + + +class TestValidation(unittest.TestCase): + + blueprint_path = path.join('resources', + 'test-keypair-validation-blueprint.yaml') + + def setUp(self): + _, fp = tempfile.mkstemp() + self.private_key = fp + _, fp = tempfile.mkstemp() + self.not_readable_private_key = fp + os.chmod(self.not_readable_private_key, 0o200) + self.temp_dir = tempfile.mkdtemp() + self.not_writable_temp_dir_r = tempfile.mkdtemp() + os.chmod(self.not_writable_temp_dir_r, 0o400) + self.not_writable_temp_dir_rx = tempfile.mkdtemp() + os.chmod(self.not_writable_temp_dir_rx, 0o500) + self.not_writable_temp_dir_rw = tempfile.mkdtemp() + os.chmod(self.not_writable_temp_dir_rw, 0o600) + + def tearDown(self): + if self.private_key: + os.remove(self.private_key) + + if self.not_readable_private_key: + os.remove(self.not_readable_private_key) + + shutil.rmtree(self.not_writable_temp_dir_r, ignore_errors=True) + shutil.rmtree(self.not_writable_temp_dir_rx, ignore_errors=True) + shutil.rmtree(self.not_writable_temp_dir_rw, ignore_errors=True) + shutil.rmtree(self.temp_dir, ignore_errors=True) + + def new_keypair_create(self, *args, **kwargs): + creation_validation(*args, **kwargs) + + def new_keypair_create_with_exception(self, *args, **kwargs): + self.assertRaises(NonRecoverableError, creation_validation, + *args, **kwargs) + + def get_keypair_inputs_private_key(self, is_external, **kwargs): + return { + 'private_key': self.private_key, + 'is_keypair_external': is_external + } + + def get_keypair_inputs_not_readable_private_key(self, + is_external, **kwargs): + return { + 'private_key': self.not_readable_private_key, + 'is_keypair_external': is_external + } + + def get_keypair_inputs_not_writable_dir_r(self, is_external, **kwargs): + return { + 'private_key': path.join(self.not_writable_temp_dir_r, + PRIVATE_KEY_NAME), + 'is_keypair_external': is_external + } + + def get_keypair_inputs_not_writable_dir_rx(self, is_external, **kwargs): + return { + 'private_key': path.join(self.not_writable_temp_dir_rx, + PRIVATE_KEY_NAME), + 'is_keypair_external': is_external + } + + def get_keypair_inputs_not_writable_dir_rw(self, is_external, **kwargs): + return { + 'private_key': path.join(self.not_writable_temp_dir_rw, + PRIVATE_KEY_NAME), + 'is_keypair_external': is_external + } + + def get_keypair_inputs_temp_dir(self, is_external, **kwargs): + return { + 'private_key': path.join(self.temp_dir, PRIVATE_KEY_NAME), + 'is_keypair_external': is_external + } + + @workflow_test(blueprint_path, inputs={ + 'private_key': '', + 'is_keypair_external': False + }) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_valid_config(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, inputs='get_keypair_inputs_private_key', + input_func_kwargs={'is_external': True}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_valid_config_external(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, inputs='get_keypair_inputs_temp_dir', + input_func_kwargs={'is_external': True}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_no_private_key(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create_with_exception): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, inputs='get_keypair_inputs_private_key', + input_func_kwargs={'is_external': False}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_local_and_exists(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create_with_exception): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, inputs='get_keypair_inputs_temp_dir', + input_func_kwargs={'is_external': False}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_local_temp_dir(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, + inputs='get_keypair_inputs_not_writable_dir_r', + input_func_kwargs={'is_external': False}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_local_non_writable_dir_r(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create_with_exception): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, + inputs='get_keypair_inputs_not_writable_dir_rx', + input_func_kwargs={'is_external': False}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_local_non_writable_dir_rx(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create_with_exception): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, + inputs='get_keypair_inputs_not_writable_dir_rw', + input_func_kwargs={'is_external': False}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_local_non_writable_dir_rw(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create_with_exception): + cfy_local.execute('install', task_retries=0) + + @workflow_test(blueprint_path, + inputs='get_keypair_inputs_not_readable_private_key', + input_func_kwargs={'is_external': True}) + @mock.patch('nova_plugin.keypair.validate_resource') + def test_keypair_not_readable_private_key(self, cfy_local, *args): + + with mock.patch('nova_plugin.keypair.create', + new=self.new_keypair_create_with_exception): + cfy_local.execute('install', task_retries=0) |