diff --git a/ec2api/api/ec2utils.py b/ec2api/api/ec2utils.py index 8c47a9b7..d0efee2f 100644 --- a/ec2api/api/ec2utils.py +++ b/ec2api/api/ec2utils.py @@ -494,6 +494,14 @@ def set_check_and_create_default_vpc(check_and_create_default_vpc): _check_and_create_default_vpc = check_and_create_default_vpc +def get_default_vpc(context): + check_and_create_default_vpc(context) + default_vpc = next((vpc for vpc in db_api.get_items(context, 'vpc') + if vpc.get('is_default')), None) + if not default_vpc: + raise exception.VPCIdNotSpecified() + return default_vpc + # NOTE(ft): following functions are copied from various parts of Nova _ephemeral = re.compile('^ephemeral(\d|[1-9]\d+)$') diff --git a/ec2api/api/instance.py b/ec2api/api/instance.py index 8252d52c..51383356 100644 --- a/ec2api/api/instance.py +++ b/ec2api/api/instance.py @@ -1158,17 +1158,12 @@ class InstanceEngineNeutron(object): network_interface, multiple_instances): # TODO(ft): support auto_assign_floating_ip - vpc_network_parameters = self.merge_network_interface_parameters( - security_group, + (security_group, + vpc_network_parameters) = self.merge_network_interface_parameters( + context, security_group, subnet_id, private_ip_address, security_group_id, network_interface) - if not vpc_network_parameters and CONF.disable_ec2_classic: - ec2utils.check_and_create_default_vpc(context) - subnet_id = self.get_default_subnet(context) - vpc_network_parameters = [{'device_index': 0, - 'subnet_id': subnet_id}] - self.check_network_interface_parameters(vpc_network_parameters, multiple_instances) @@ -1191,21 +1186,6 @@ class InstanceEngineNeutron(object): return vpc_id, launch_context - def get_default_subnet(self, context): - default_vpc = next( - (vpc for vpc in db_api.get_items(context, 'vpc') - if vpc.get('is_default')), None) - if not default_vpc: - raise exception.VPCIdNotSpecified() - subnet = next( - (subnet for subnet in db_api.get_items(context, 'subnet') - if subnet['vpc_id'] == default_vpc['id']), None) - if not subnet: - raise exception.MissingInput( - _("No subnets found for the default VPC '%s'. " - "Please specify a subnet.") % default_vpc['id']) - return subnet['id'] - def get_launch_extra_parameters(self, context, cleaner, launch_context): if 'ec2_classic_nics' in launch_context: nics = launch_context['ec2_classic_nics'] @@ -1255,39 +1235,39 @@ class InstanceEngineNeutron(object): return ec2_network_interfaces def merge_network_interface_parameters(self, + context, security_group_names, subnet_id, private_ip_address, security_group_ids, network_interfaces): - network_interfaces = network_interfaces or [] if ((subnet_id or private_ip_address or security_group_ids or - security_group_names) and - (len(network_interfaces) > 1 or - # NOTE(ft): the only case in AWS when simple subnet_id - # and/or private_ip_address parameters are compatible with - # network_interface parameter is default behavior change of - # public IP association for passed subnet_id by specifying - # the only element in network_interfaces: - # {"device_index": 0, - # "associate_public_ip_address": } - # Both keys must be in the dict, and no other keys - # are allowed - # We should support such combination of parameters for - # compatibility purposes, even if we ignore - # associate_public_ip_address in all other code - len(network_interfaces) == 1 and - (len(network_interfaces[0]) != 2 or - ('associate_public_ip_address' not in - network_interfaces[0]) or - network_interfaces[0].get('device_index') != 0))): + security_group_names) and network_interfaces): msg = _(' Network interfaces and an instance-level subnet ID or ' 'private IP address or security groups may not be ' 'specified on the same request') raise exception.InvalidParameterCombination(msg) - if subnet_id: + if network_interfaces: + if (CONF.disable_ec2_classic and + len(network_interfaces) == 1 and + # NOTE(tikitavi): the case in AWS CLI when security_group_ids + # and/or private_ip_address parameters are set with + # network_interface parameter having + # associate_public_ip_address setting + # private_ip_address and security_group_ids in that case + # go to network_interface parameter + 'associate_public_ip_address' in network_interfaces[0] and + 'device_index' in network_interfaces[0] and + network_interfaces[0]['device_index'] == 0 and + ('subnet_id' not in network_interfaces[0] or + 'network_interface_id' not in network_interfaces[0])): + + subnet_id = self.get_default_subnet(context)['id'] + network_interfaces[0]['subnet_id'] = subnet_id + return None, network_interfaces + elif subnet_id: if security_group_names: msg = _('The parameter groupName cannot be used with ' 'the parameter subnet') @@ -1298,7 +1278,25 @@ class InstanceEngineNeutron(object): param['private_ip_address'] = private_ip_address if security_group_ids: param['security_group_id'] = security_group_ids - return [param] + return None, [param] + elif CONF.disable_ec2_classic: + subnet_id = self.get_default_subnet(context)['id'] + param = {'device_index': 0, + 'subnet_id': subnet_id} + if security_group_ids or security_group_names: + security_group_id = security_group_ids or [] + if security_group_names: + security_groups = ( + security_group_api.describe_security_groups( + context, group_name=security_group_names) + ['securityGroupInfo']) + security_group_id.extend(sg['groupId'] + for sg in security_groups) + + param['security_group_id'] = security_group_id + if private_ip_address: + param['private_ip_address'] = private_ip_address + return None, [param] elif private_ip_address: msg = _('Specifying an IP address is only valid for VPC instances ' 'and thus requires a subnet in which to launch') @@ -1307,8 +1305,18 @@ class InstanceEngineNeutron(object): msg = _('VPC security groups may not be used for a non-VPC launch') raise exception.InvalidParameterCombination(msg) else: - # NOTE(ft): only one of this variables is not empty - return network_interfaces + return security_group_names, [] + + def get_default_subnet(self, context): + default_vpc = ec2utils.get_default_vpc(context) + subnet = next( + (subnet for subnet in db_api.get_items(context, 'subnet') + if subnet['vpc_id'] == default_vpc['id']), None) + if not subnet: + raise exception.MissingInput( + _("No subnets found for the default VPC '%s'. " + "Please specify a subnet.") % default_vpc['id']) + return subnet def check_network_interface_parameters(self, params, multiple_instances): # NOTE(ft): we ignore associate_public_ip_address diff --git a/ec2api/tests/unit/test_instance.py b/ec2api/tests/unit/test_instance.py index 2659dc62..520c3d78 100644 --- a/ec2api/tests/unit/test_instance.py +++ b/ec2api/tests/unit/test_instance.py @@ -20,7 +20,6 @@ import random import mock from novaclient import exceptions as nova_exception -from oslotest import base as test_base import six from ec2api.api import instance as instance_api @@ -1169,98 +1168,138 @@ class InstanceTestCase(base.ApiTestCase): # TODO(ft): add tests for get_vpc_default_security_group_id, -class InstancePrivateTestCase(test_base.BaseTestCase): +class InstancePrivateTestCase(base.BaseTestCase): def test_merge_network_interface_parameters(self): + fake_context = base.create_context() engine = instance_api.InstanceEngineNeutron() self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, 'subnet-1', None, None, + fake_context, None, 'subnet-1', None, None, [{'device_index': 0, 'private_ip_address': '10.10.10.10'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, None, '10.10.10.10', None, + fake_context, None, None, '10.10.10.10', None, [{'device_index': 0, 'subnet_id': 'subnet-1'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - ['default'], None, None, None, + fake_context, ['default'], None, None, None, [{'device_index': 0, 'subnet_id': 'subnet-1'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, None, None, ['sg-1'], + fake_context, None, None, None, ['sg-1'], [{'device_index': 0, 'subnet_id': 'subnet-1'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, 'subnet-1', None, None, + fake_context, None, 'subnet-1', None, None, [{'device_index': 1, 'associate_public_ip_address': True}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, 'subnet-1', None, None, + fake_context, None, 'subnet-1', None, None, [{'device_index': 0, 'associate_public_ip_address': True}, {'device_index': 1, 'subnet_id': 'subnet-2'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, 'subnet-1', None, None, + fake_context, None, 'subnet-1', None, None, [{'device_index': 0}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - ['default'], 'subnet-1', None, None, None) + fake_context, ['default'], 'subnet-1', None, None, None) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, None, '10.10.10.10', None, None) + fake_context, None, None, '10.10.10.10', None, None) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, - None, None, None, ['sg-1'], None) + fake_context, None, None, None, ['sg-1'], None) self.assertEqual( - ([{'device_index': 0, - 'subnet_id': 'subnet-1'}]), + (None, [{'device_index': 0, + 'subnet_id': 'subnet-1'}]), engine.merge_network_interface_parameters( - None, 'subnet-1', None, None, None)) + fake_context, None, 'subnet-1', None, None, None)) self.assertEqual( - ([{'device_index': 0, - 'subnet_id': 'subnet-1', - 'private_ip_address': '10.10.10.10'}]), + (None, [{'device_index': 0, + 'subnet_id': 'subnet-1', + 'private_ip_address': '10.10.10.10'}]), engine.merge_network_interface_parameters( - None, 'subnet-1', '10.10.10.10', None, None)) + fake_context, None, 'subnet-1', '10.10.10.10', None, None)) self.assertEqual( - ([{'device_index': 0, - 'subnet_id': 'subnet-1', - 'private_ip_address': '10.10.10.10', - 'security_group_id': ['sg-1']}]), + (None, [{'device_index': 0, + 'subnet_id': 'subnet-1', + 'private_ip_address': '10.10.10.10', + 'security_group_id': ['sg-1']}]), engine.merge_network_interface_parameters( - None, 'subnet-1', '10.10.10.10', ['sg-1'], None)) + fake_context, None, 'subnet-1', '10.10.10.10', ['sg-1'], None)) self.assertEqual( - ([{'device_index': 0, - 'subnet_id': 'subnet-1', - 'security_group_id': ['sg-1']}]), + (None, [{'device_index': 0, + 'subnet_id': 'subnet-1', + 'security_group_id': ['sg-1']}]), engine.merge_network_interface_parameters( - None, 'subnet-1', None, ['sg-1'], None)) + fake_context, None, 'subnet-1', None, ['sg-1'], None)) self.assertEqual( - ([{'device_index': 0, - 'subnet_id': 'subnet-1'}]), + (None, [{'device_index': 0, + 'subnet_id': 'subnet-1'}]), engine.merge_network_interface_parameters( - None, None, None, None, + fake_context, None, None, None, None, [{'device_index': 0, 'subnet_id': 'subnet-1'}])) - self.assertEqual([], + self.assertEqual((['default'], []), engine.merge_network_interface_parameters( - ['default'], None, None, None, None)) - self.assertEqual([], + fake_context, ['default'], None, None, None, + None)) + self.assertEqual((None, []), engine.merge_network_interface_parameters( - None, None, None, None, None)) + fake_context, None, None, None, None, None)) + + self.configure(disable_ec2_classic=True) + self.db_api = self.mock_db() + self.db_api.set_mock_items(fakes.DB_VPC_DEFAULT, + fakes.DB_SUBNET_DEFAULT) + + self.assertEqual( + (None, [{'device_index': 0, + 'subnet_id': fakes.ID_EC2_SUBNET_DEFAULT}]), + engine.merge_network_interface_parameters( + fake_context, None, None, None, None, None)) + self.assertEqual( + (None, [{'device_index': 0, + 'subnet_id': fakes.ID_EC2_SUBNET_DEFAULT, + 'security_group_id': ['sg-id'], + 'associate_public_ip_address': True}]), + engine.merge_network_interface_parameters( + fake_context, None, None, None, None, + [{'device_index': 0, + 'associate_public_ip_address': True, + 'security_group_id': ['sg-id']}])) + + with mock.patch('ec2api.api.security_group.describe_security_groups' + ) as describe_sg: + + describe_sg.return_value = { + 'securityGroupInfo': [{'groupId': 'sg-named-id'}] + } + self.assertEqual((None, [{'device_index': 0, + 'subnet_id': fakes.ID_EC2_SUBNET_DEFAULT, + 'security_group_id': ['sg-id', + 'sg-named-id'], + 'private_ip_address': 'private-ip'}]), + engine.merge_network_interface_parameters( + fake_context, ['sg-name'], None, + 'private-ip', ['sg-id'], None)) + describe_sg.assert_called_once_with(mock.ANY, + group_name=['sg-name']) def test_check_network_interface_parameters(self): engine = instance_api.InstanceEngineNeutron()