Merge "Changes in address in default VPC mode"
This commit is contained in:
commit
5d45aea122
|
@ -210,7 +210,8 @@ def _disassociate_address_item(context, address):
|
||||||
class AddressEngineNeutron(object):
|
class AddressEngineNeutron(object):
|
||||||
|
|
||||||
def allocate_address(self, context, domain=None):
|
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)
|
return AddressEngineNova().allocate_address(context)
|
||||||
os_public_network = ec2utils.get_os_public_network(context)
|
os_public_network = ec2utils.get_os_public_network(context)
|
||||||
neutron = clients.neutron(context)
|
neutron = clients.neutron(context)
|
||||||
|
@ -248,9 +249,27 @@ class AddressEngineNeutron(object):
|
||||||
if not _is_address_valid(context, neutron, address):
|
if not _is_address_valid(context, neutron, address):
|
||||||
raise exception.InvalidAllocationIDNotFound(
|
raise exception.InvalidAllocationIDNotFound(
|
||||||
id=allocation_id)
|
id=allocation_id)
|
||||||
|
|
||||||
if 'network_interface_id' in address:
|
if 'network_interface_id' in address:
|
||||||
raise exception.InvalidIPAddressInUse(
|
if CONF.disable_ec2_classic:
|
||||||
ip_address=address['public_ip'])
|
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:
|
with common.OnCrashCleaner() as cleaner:
|
||||||
db_api.delete_item(context, address['id'])
|
db_api.delete_item(context, address['id'])
|
||||||
|
@ -272,28 +291,37 @@ class AddressEngineNeutron(object):
|
||||||
instance_network_interfaces.append(eni)
|
instance_network_interfaces.append(eni)
|
||||||
|
|
||||||
neutron = clients.neutron(context)
|
neutron = clients.neutron(context)
|
||||||
|
|
||||||
if public_ip:
|
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
|
# TODO(ft): implement search in DB layer
|
||||||
address = next((addr for addr in db_api.get_items(context,
|
address = next((addr for addr in db_api.get_items(context,
|
||||||
'eipalloc')
|
'eipalloc')
|
||||||
if addr['public_ip'] == public_ip), None)
|
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.")
|
msg = _("The address '%(public_ip)s' does not belong to you.")
|
||||||
raise exception.AuthFailure(msg % {'public_ip': public_ip})
|
raise exception.AuthFailure(msg % {'public_ip': public_ip})
|
||||||
|
allocation_id = address['id']
|
||||||
# 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 instance_id:
|
if instance_id:
|
||||||
if not instance_network_interfaces:
|
if not instance_network_interfaces:
|
||||||
|
@ -355,20 +383,33 @@ class AddressEngineNeutron(object):
|
||||||
def disassociate_address(self, context, public_ip=None,
|
def disassociate_address(self, context, public_ip=None,
|
||||||
association_id=None):
|
association_id=None):
|
||||||
neutron = clients.neutron(context)
|
neutron = clients.neutron(context)
|
||||||
|
|
||||||
if public_ip:
|
if public_ip:
|
||||||
# TODO(ft): implement search in DB layer
|
# TODO(ft): implement search in DB layer
|
||||||
address = next((addr for addr in db_api.get_items(context,
|
address = next((addr for addr in db_api.get_items(context,
|
||||||
'eipalloc')
|
'eipalloc')
|
||||||
if addr['public_ip'] == public_ip), None)
|
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 '
|
msg = _('You must specify an association id when unmapping '
|
||||||
'an address from a VPC instance')
|
'an address from a VPC instance')
|
||||||
raise exception.InvalidParameterValue(msg)
|
raise exception.InvalidParameterValue(msg)
|
||||||
# NOTE(ft): association_id is unused in EC2 Classic mode, but it's
|
association_id = ec2utils.change_ec2_id_kind(address['id'],
|
||||||
# passed there to validate its emptiness in one place
|
'eipassoc')
|
||||||
return AddressEngineNova().disassociate_address(
|
|
||||||
context, public_ip=public_ip,
|
|
||||||
association_id=association_id)
|
|
||||||
|
|
||||||
address = db_api.get_item_by_id(
|
address = db_api.get_item_by_id(
|
||||||
context, ec2utils.change_ec2_id_kind(association_id, 'eipalloc'))
|
context, ec2utils.change_ec2_id_kind(association_id, 'eipalloc'))
|
||||||
|
|
|
@ -145,8 +145,11 @@ ID_EC2_DHCP_OPTIONS_2 = random_ec2_id('dopt')
|
||||||
|
|
||||||
|
|
||||||
# address constants
|
# address constants
|
||||||
|
ID_EC2_ADDRESS_DEFAULT = random_ec2_id('eipalloc')
|
||||||
ID_EC2_ADDRESS_1 = random_ec2_id('eipalloc')
|
ID_EC2_ADDRESS_1 = random_ec2_id('eipalloc')
|
||||||
ID_EC2_ADDRESS_2 = 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_1 = ID_EC2_ADDRESS_1.replace('eipalloc', 'eipassoc')
|
||||||
ID_EC2_ASSOCIATION_2 = ID_EC2_ADDRESS_2.replace('eipalloc', 'eipassoc')
|
ID_EC2_ASSOCIATION_2 = ID_EC2_ADDRESS_2.replace('eipalloc', 'eipassoc')
|
||||||
ID_OS_FLOATING_IP_1 = random_os_id()
|
ID_OS_FLOATING_IP_1 = random_os_id()
|
||||||
|
@ -1043,6 +1046,14 @@ class NovaFloatingIp(object):
|
||||||
self.fixed_ip = nova_ip_dict['fixed_ip']
|
self.fixed_ip = nova_ip_dict['fixed_ip']
|
||||||
self.instance_id = nova_ip_dict['instance_id']
|
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 = {
|
DB_ADDRESS_1 = {
|
||||||
'id': ID_EC2_ADDRESS_1,
|
'id': ID_EC2_ADDRESS_1,
|
||||||
'os_id': ID_OS_FLOATING_IP_1,
|
'os_id': ID_OS_FLOATING_IP_1,
|
||||||
|
@ -1083,6 +1094,16 @@ EC2_ADDRESS_2 = {
|
||||||
'privateIpAddress': IP_NETWORK_INTERFACE_2,
|
'privateIpAddress': IP_NETWORK_INTERFACE_2,
|
||||||
'networkInterfaceOwnerId': ID_OS_PROJECT,
|
'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 = {
|
OS_FLOATING_IP_1 = {
|
||||||
'id': ID_OS_FLOATING_IP_1,
|
'id': ID_OS_FLOATING_IP_1,
|
||||||
|
|
|
@ -57,6 +57,28 @@ class AddressTestCase(base.ApiTestCase):
|
||||||
|
|
||||||
resp = self.execute('AllocateAddress', {'Domain': 'vpc'})
|
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(fakes.IP_ADDRESS_1, resp['publicIp'])
|
||||||
self.assertEqual('vpc', resp['domain'])
|
self.assertEqual('vpc', resp['domain'])
|
||||||
self.assertEqual(fakes.ID_EC2_ADDRESS_1,
|
self.assertEqual(fakes.ID_EC2_ADDRESS_1,
|
||||||
|
@ -192,6 +214,14 @@ class AddressTestCase(base.ApiTestCase):
|
||||||
'AllowReassociation': 'True'},
|
'AllowReassociation': 'True'},
|
||||||
fakes.IP_NETWORK_INTERFACE_2)
|
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):
|
def test_associate_address_vpc_idempotent(self):
|
||||||
address.address_engine = (
|
address.address_engine = (
|
||||||
address.AddressEngineNeutron())
|
address.AddressEngineNeutron())
|
||||||
|
@ -338,6 +368,17 @@ class AddressTestCase(base.ApiTestCase):
|
||||||
{'AllocationId': fakes.ID_EC2_ADDRESS_1,
|
{'AllocationId': fakes.ID_EC2_ADDRESS_1,
|
||||||
'InstanceId': fakes.ID_EC2_INSTANCE_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
|
@tools.screen_unexpected_exception_logs
|
||||||
def test_associate_address_vpc_rollback(self):
|
def test_associate_address_vpc_rollback(self):
|
||||||
address.address_engine = (
|
address.address_engine = (
|
||||||
|
@ -388,6 +429,22 @@ class AddressTestCase(base.ApiTestCase):
|
||||||
{'AssociationId': fakes.ID_EC2_ASSOCIATION_2})
|
{'AssociationId': fakes.ID_EC2_ASSOCIATION_2})
|
||||||
self.assertEqual(True, resp['return'])
|
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(
|
self.neutron.update_floatingip.assert_called_once_with(
|
||||||
fakes.ID_OS_FLOATING_IP_2,
|
fakes.ID_OS_FLOATING_IP_2,
|
||||||
{'floatingip': {'port_id': None}})
|
{'floatingip': {'port_id': None}})
|
||||||
|
@ -448,6 +505,18 @@ class AddressTestCase(base.ApiTestCase):
|
||||||
do_check({'AssociationId': fakes.ID_EC2_ASSOCIATION_2},
|
do_check({'AssociationId': fakes.ID_EC2_ASSOCIATION_2},
|
||||||
'InvalidAssociationID.NotFound')
|
'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
|
@tools.screen_unexpected_exception_logs
|
||||||
def test_dissassociate_address_vpc_rollback(self):
|
def test_dissassociate_address_vpc_rollback(self):
|
||||||
address.address_engine = (
|
address.address_engine = (
|
||||||
|
@ -496,6 +565,28 @@ class AddressTestCase(base.ApiTestCase):
|
||||||
self.db_api.delete_item.assert_called_once_with(
|
self.db_api.delete_item.assert_called_once_with(
|
||||||
mock.ANY, fakes.ID_EC2_ADDRESS_1)
|
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):
|
def test_release_address_invalid_parameters(self):
|
||||||
address.address_engine = (
|
address.address_engine = (
|
||||||
address.AddressEngineNeutron())
|
address.AddressEngineNeutron())
|
||||||
|
@ -541,6 +632,18 @@ class AddressTestCase(base.ApiTestCase):
|
||||||
do_check({'AllocationId': fakes.ID_EC2_ADDRESS_2},
|
do_check({'AllocationId': fakes.ID_EC2_ADDRESS_2},
|
||||||
'InvalidIPAddress.InUse')
|
'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
|
@tools.screen_unexpected_exception_logs
|
||||||
def test_release_address_vpc_rollback(self):
|
def test_release_address_vpc_rollback(self):
|
||||||
address.address_engine = (
|
address.address_engine = (
|
||||||
|
|
Loading…
Reference in New Issue