Merge "Changes in address in default VPC mode"

This commit is contained in:
Jenkins 2017-02-27 08:50:05 +00:00 committed by Gerrit Code Review
commit 5d45aea122
3 changed files with 189 additions and 24 deletions

View File

@ -210,7 +210,8 @@ def _disassociate_address_item(context, address):
class AddressEngineNeutron(object):
def allocate_address(self, context, domain=None):
if not domain or domain == 'standard':
if ((not domain or domain == 'standard') and
not CONF.disable_ec2_classic):
return AddressEngineNova().allocate_address(context)
os_public_network = ec2utils.get_os_public_network(context)
neutron = clients.neutron(context)
@ -248,9 +249,27 @@ class AddressEngineNeutron(object):
if not _is_address_valid(context, neutron, address):
raise exception.InvalidAllocationIDNotFound(
id=allocation_id)
if 'network_interface_id' in address:
raise exception.InvalidIPAddressInUse(
ip_address=address['public_ip'])
if CONF.disable_ec2_classic:
network_interface_id = address['network_interface_id']
network_interface = db_api.get_item_by_id(context,
network_interface_id)
default_vpc = ec2utils.check_and_create_default_vpc(context)
if default_vpc:
default_vpc_id = default_vpc['id']
if (network_interface and
network_interface['vpc_id'] == default_vpc_id):
association_id = ec2utils.change_ec2_id_kind(address['id'],
'eipassoc')
self.disassociate_address(
context, association_id=association_id)
else:
raise exception.InvalidIPAddressInUse(
ip_address=address['public_ip'])
else:
raise exception.InvalidIPAddressInUse(
ip_address=address['public_ip'])
with common.OnCrashCleaner() as cleaner:
db_api.delete_item(context, address['id'])
@ -272,28 +291,37 @@ class AddressEngineNeutron(object):
instance_network_interfaces.append(eni)
neutron = clients.neutron(context)
if public_ip:
if instance_network_interfaces:
msg = _('You must specify an allocation id when mapping '
'an address to a VPC instance')
raise exception.InvalidParameterCombination(msg)
# TODO(ft): implement search in DB layer
address = next((addr for addr in db_api.get_items(context,
'eipalloc')
if addr['public_ip'] == public_ip), None)
if address and _is_address_valid(context, neutron, address):
if not CONF.disable_ec2_classic:
if instance_network_interfaces:
msg = _('You must specify an allocation id when mapping '
'an address to a VPC instance')
raise exception.InvalidParameterCombination(msg)
if address and _is_address_valid(context, neutron, address):
msg = _(
"The address '%(public_ip)s' does not belong to you.")
raise exception.AuthFailure(msg % {'public_ip': public_ip})
# NOTE(ft): in fact only the first two parameters are used to
# associate an address in EC2 Classic mode. Other parameters
# are sent to validate their emptiness in one place
return AddressEngineNova().associate_address(
context, public_ip=public_ip, instance_id=instance_id,
allocation_id=allocation_id,
network_interface_id=network_interface_id,
private_ip_address=private_ip_address,
allow_reassociation=allow_reassociation)
if not address:
msg = _("The address '%(public_ip)s' does not belong to you.")
raise exception.AuthFailure(msg % {'public_ip': public_ip})
# NOTE(ft): in fact only the first two parameters are used to
# associate an address in EC2 Classic mode. Other parameters are
# sent to validate their emptiness in one place
return AddressEngineNova().associate_address(
context, public_ip=public_ip, instance_id=instance_id,
allocation_id=allocation_id,
network_interface_id=network_interface_id,
private_ip_address=private_ip_address,
allow_reassociation=allow_reassociation)
allocation_id = address['id']
if instance_id:
if not instance_network_interfaces:
@ -355,20 +383,33 @@ class AddressEngineNeutron(object):
def disassociate_address(self, context, public_ip=None,
association_id=None):
neutron = clients.neutron(context)
if public_ip:
# TODO(ft): implement search in DB layer
address = next((addr for addr in db_api.get_items(context,
'eipalloc')
if addr['public_ip'] == public_ip), None)
if address and _is_address_valid(context, neutron, address):
if not CONF.disable_ec2_classic:
if address and _is_address_valid(context, neutron, address):
msg = _('You must specify an association id when '
'unmapping an address from a VPC instance')
raise exception.InvalidParameterValue(msg)
# NOTE(ft): association_id is unused in EC2 Classic mode,
# but it's passed there to validate its emptiness in one place
return AddressEngineNova().disassociate_address(
context, public_ip=public_ip,
association_id=association_id)
if not address:
msg = _("The address '%(public_ip)s' does not belong to you.")
raise exception.AuthFailure(msg % {'public_ip': public_ip})
if 'network_interface_id' not in address:
msg = _('You must specify an association id when unmapping '
'an address from a VPC instance')
raise exception.InvalidParameterValue(msg)
# NOTE(ft): association_id is unused in EC2 Classic mode, but it's
# passed there to validate its emptiness in one place
return AddressEngineNova().disassociate_address(
context, public_ip=public_ip,
association_id=association_id)
association_id = ec2utils.change_ec2_id_kind(address['id'],
'eipassoc')
address = db_api.get_item_by_id(
context, ec2utils.change_ec2_id_kind(association_id, 'eipalloc'))

View File

@ -145,8 +145,11 @@ ID_EC2_DHCP_OPTIONS_2 = random_ec2_id('dopt')
# address constants
ID_EC2_ADDRESS_DEFAULT = random_ec2_id('eipalloc')
ID_EC2_ADDRESS_1 = random_ec2_id('eipalloc')
ID_EC2_ADDRESS_2 = random_ec2_id('eipalloc')
ID_EC2_ASSOCIATION_DEFAULT = ID_EC2_ADDRESS_DEFAULT.replace('eipalloc',
'eipassoc')
ID_EC2_ASSOCIATION_1 = ID_EC2_ADDRESS_1.replace('eipalloc', 'eipassoc')
ID_EC2_ASSOCIATION_2 = ID_EC2_ADDRESS_2.replace('eipalloc', 'eipassoc')
ID_OS_FLOATING_IP_1 = random_os_id()
@ -1043,6 +1046,14 @@ class NovaFloatingIp(object):
self.fixed_ip = nova_ip_dict['fixed_ip']
self.instance_id = nova_ip_dict['instance_id']
DB_ADDRESS_DEFAULT = {
'id': ID_EC2_ADDRESS_DEFAULT,
'os_id': ID_OS_FLOATING_IP_2,
'vpc_id': None,
'public_ip': IP_ADDRESS_2,
'network_interface_id': ID_EC2_NETWORK_INTERFACE_DEFAULT,
'private_ip_address': IP_NETWORK_INTERFACE_DEFAULT,
}
DB_ADDRESS_1 = {
'id': ID_EC2_ADDRESS_1,
'os_id': ID_OS_FLOATING_IP_1,
@ -1083,6 +1094,16 @@ EC2_ADDRESS_2 = {
'privateIpAddress': IP_NETWORK_INTERFACE_2,
'networkInterfaceOwnerId': ID_OS_PROJECT,
}
EC2_ADDRESS_DEFAULT = {
'allocationId': ID_EC2_ADDRESS_DEFAULT,
'publicIp': IP_ADDRESS_2,
'domain': 'vpc',
'instanceId': ID_EC2_INSTANCE_DEFAULT,
'associationId': ID_EC2_ASSOCIATION_DEFAULT,
'networkInterfaceId': ID_EC2_NETWORK_INTERFACE_DEFAULT,
'privateIpAddress': IP_NETWORK_INTERFACE_DEFAULT,
'networkInterfaceOwnerId': ID_OS_PROJECT,
}
OS_FLOATING_IP_1 = {
'id': ID_OS_FLOATING_IP_1,

View File

@ -57,6 +57,28 @@ class AddressTestCase(base.ApiTestCase):
resp = self.execute('AllocateAddress', {'Domain': 'vpc'})
self.assertEqual(fakes.IP_ADDRESS_1, resp['publicIp'])
self.assertEqual('vpc', resp['domain'])
self.assertEqual(fakes.ID_EC2_ADDRESS_1,
resp['allocationId'])
self.db_api.add_item.assert_called_once_with(
mock.ANY, 'eipalloc',
tools.purge_dict(fakes.DB_ADDRESS_1,
('id', 'vpc_id')))
self.neutron.create_floatingip.assert_called_once_with(
{'floatingip': {
'floating_network_id':
fakes.ID_OS_PUBLIC_NETWORK}})
self.neutron.list_networks.assert_called_once_with(
**{'router:external': True,
'name': fakes.NAME_OS_PUBLIC_NETWORK})
self.db_api.reset_mock()
self.neutron.create_floatingip.reset_mock()
self.neutron.list_networks.reset_mock()
self.configure(disable_ec2_classic=True)
resp = self.execute('AllocateAddress', {})
self.assertEqual(fakes.IP_ADDRESS_1, resp['publicIp'])
self.assertEqual('vpc', resp['domain'])
self.assertEqual(fakes.ID_EC2_ADDRESS_1,
@ -192,6 +214,14 @@ class AddressTestCase(base.ApiTestCase):
'AllowReassociation': 'True'},
fakes.IP_NETWORK_INTERFACE_2)
self.configure(disable_ec2_classic=True)
self.set_mock_db_items(
fakes.DB_VPC_DEFAULT, fakes.DB_ADDRESS_1, fakes.DB_IGW_1,
fakes.DB_NETWORK_INTERFACE_2)
do_check({'PublicIp': fakes.IP_ADDRESS_1,
'InstanceId': fakes.ID_EC2_INSTANCE_1},
fakes.IP_NETWORK_INTERFACE_2)
def test_associate_address_vpc_idempotent(self):
address.address_engine = (
address.AddressEngineNeutron())
@ -338,6 +368,17 @@ class AddressTestCase(base.ApiTestCase):
{'AllocationId': fakes.ID_EC2_ADDRESS_1,
'InstanceId': fakes.ID_EC2_INSTANCE_1})
# NOTE(tikitavi): associate to wrong public ip
self.configure(disable_ec2_classic=True)
self.set_mock_db_items(
fakes.DB_VPC_DEFAULT, fakes.DB_IGW_DEFAULT, fakes.DB_ADDRESS_1,
fakes.DB_INSTANCE_DEFAULT, tools.update_dict(
fakes.DB_NETWORK_INTERFACE_DEFAULT,
{'instance_id': fakes.ID_EC2_INSTANCE_DEFAULT}))
do_check({'PublicIp': '0.0.0.0',
'InstanceId': fakes.ID_EC2_INSTANCE_DEFAULT},
'AuthFailure')
@tools.screen_unexpected_exception_logs
def test_associate_address_vpc_rollback(self):
address.address_engine = (
@ -388,6 +429,22 @@ class AddressTestCase(base.ApiTestCase):
{'AssociationId': fakes.ID_EC2_ASSOCIATION_2})
self.assertEqual(True, resp['return'])
self.neutron.update_floatingip.assert_called_once_with(
fakes.ID_OS_FLOATING_IP_2,
{'floatingip': {'port_id': None}})
self.db_api.update_item.assert_called_once_with(
mock.ANY,
tools.purge_dict(fakes.DB_ADDRESS_2, ['network_interface_id',
'private_ip_address']))
self.neutron.update_floatingip.reset_mock()
self.db_api.update_item.reset_mock()
self.configure(disable_ec2_classic=True)
resp = self.execute('DisassociateAddress',
{'PublicIp': fakes.IP_ADDRESS_2})
self.assertEqual(True, resp['return'])
self.neutron.update_floatingip.assert_called_once_with(
fakes.ID_OS_FLOATING_IP_2,
{'floatingip': {'port_id': None}})
@ -448,6 +505,18 @@ class AddressTestCase(base.ApiTestCase):
do_check({'AssociationId': fakes.ID_EC2_ASSOCIATION_2},
'InvalidAssociationID.NotFound')
# NOTE(tikitavi): disassociate to wrong public ip
self.configure(disable_ec2_classic=True)
self.set_mock_db_items()
self.assert_execution_error('AuthFailure', 'DisassociateAddress',
{'PublicIp': fakes.IP_ADDRESS_2})
# NOTE(tikitavi): disassociate to unassociated ip
self.set_mock_db_items(fakes.DB_ADDRESS_1)
self.assert_execution_error('InvalidParameterValue',
'DisassociateAddress',
{'PublicIp': fakes.IP_ADDRESS_1})
@tools.screen_unexpected_exception_logs
def test_dissassociate_address_vpc_rollback(self):
address.address_engine = (
@ -496,6 +565,28 @@ class AddressTestCase(base.ApiTestCase):
self.db_api.delete_item.assert_called_once_with(
mock.ANY, fakes.ID_EC2_ADDRESS_1)
@mock.patch('ec2api.api.address.AddressEngineNeutron.disassociate_address')
def test_release_address_default_vpc(self, disassociate_address):
address.address_engine = (
address.AddressEngineNeutron())
self.configure(disable_ec2_classic=True)
self.set_mock_db_items(fakes.DB_VPC_DEFAULT,
fakes.DB_ADDRESS_DEFAULT,
fakes.DB_NETWORK_INTERFACE_DEFAULT)
self.neutron.show_floatingip.return_value = (
{'floatingip': fakes.OS_FLOATING_IP_2})
resp = self.execute('ReleaseAddress',
{'AllocationId': fakes.ID_EC2_ADDRESS_DEFAULT})
self.assertEqual(True, resp['return'])
disassociate_address.assert_called_once_with(
mock.ANY, association_id=fakes.ID_EC2_ASSOCIATION_DEFAULT)
self.neutron.delete_floatingip.assert_called_once_with(
fakes.ID_OS_FLOATING_IP_2)
self.db_api.delete_item.assert_called_once_with(
mock.ANY, fakes.ID_EC2_ADDRESS_DEFAULT)
def test_release_address_invalid_parameters(self):
address.address_engine = (
address.AddressEngineNeutron())
@ -541,6 +632,18 @@ class AddressTestCase(base.ApiTestCase):
do_check({'AllocationId': fakes.ID_EC2_ADDRESS_2},
'InvalidIPAddress.InUse')
# NOTE(tikitavi): address is in use in not default vpc
self.configure(disable_ec2_classic=True)
self.set_mock_db_items(fakes.DB_VPC_DEFAULT,
fakes.DB_VPC_1,
fakes.DB_ADDRESS_2,
fakes.DB_NETWORK_INTERFACE_2)
self.neutron.show_floatingip.return_value = (
{'floatingip': fakes.OS_FLOATING_IP_2})
do_check({'AllocationId': fakes.ID_EC2_ADDRESS_2},
'InvalidIPAddress.InUse')
@tools.screen_unexpected_exception_logs
def test_release_address_vpc_rollback(self):
address.address_engine = (