6058f48d47
Change-Id: Ifec457bef42a4276ae0bd9f2b38ffc5712b3b3b7
809 lines
37 KiB
Python
809 lines
37 KiB
Python
# Copyright 2014
|
|
# The Cloudscaling Group, Inc.
|
|
#
|
|
# 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 copy
|
|
|
|
import mock
|
|
|
|
from ec2api.api import common
|
|
from ec2api.api import ec2utils
|
|
from ec2api.api import route_table
|
|
from ec2api.tests.unit import base
|
|
from ec2api.tests.unit import fakes
|
|
from ec2api.tests.unit import matchers
|
|
from ec2api.tests.unit import tools
|
|
|
|
|
|
class RouteTableTestCase(base.ApiTestCase):
|
|
|
|
def test_route_table_create(self):
|
|
self.set_mock_db_items(fakes.DB_VPC_1)
|
|
self.db_api.add_item.side_effect = (
|
|
tools.get_db_api_add_item(fakes.ID_EC2_ROUTE_TABLE_1))
|
|
resp = self.execute('CreateRouteTable',
|
|
{'VpcId': fakes.ID_EC2_VPC_1})
|
|
self.assertThat(
|
|
resp['routeTable'],
|
|
matchers.DictMatches(tools.purge_dict(fakes.EC2_ROUTE_TABLE_1,
|
|
('associationSet',))))
|
|
self.db_api.add_item.assert_called_once_with(
|
|
mock.ANY,
|
|
'rtb',
|
|
{'vpc_id': fakes.ID_EC2_VPC_1,
|
|
'routes': [{'destination_cidr_block': fakes.CIDR_VPC_1,
|
|
'gateway_id': None}]})
|
|
self.db_api.get_item_by_id.assert_called_once_with(
|
|
mock.ANY, fakes.ID_EC2_VPC_1)
|
|
|
|
def test_route_table_create_invalid_parameters(self):
|
|
self.set_mock_db_items()
|
|
self.assert_execution_error(
|
|
'InvalidVpcID.NotFound', 'CreateRouteTable',
|
|
{'VpcId': fakes.ID_EC2_VPC_1})
|
|
|
|
@mock.patch('ec2api.api.route_table._update_routes_in_associated_subnets')
|
|
def test_create_route(self, routes_updater):
|
|
self.set_mock_db_items(
|
|
fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_2,
|
|
fakes.DB_VPC_1, fakes.DB_IGW_1,
|
|
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2)
|
|
|
|
def do_check(params, route_table, rollback_route_table_state):
|
|
resp = self.execute('CreateRoute', params)
|
|
self.assertEqual(True, resp['return'])
|
|
|
|
self.db_api.update_item.assert_called_once_with(
|
|
mock.ANY, route_table)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, route_table, mock.ANY,
|
|
rollback_route_table_state)
|
|
|
|
self.db_api.update_item.reset_mock()
|
|
routes_updater.reset_mock()
|
|
|
|
route_table = copy.deepcopy(fakes.DB_ROUTE_TABLE_1)
|
|
route_table['routes'].append({'gateway_id': fakes.ID_EC2_IGW_1,
|
|
'destination_cidr_block': '0.0.0.0/0'})
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': '0.0.0.0/0',
|
|
'GatewayId': fakes.ID_EC2_IGW_1},
|
|
route_table, fakes.DB_ROUTE_TABLE_1)
|
|
|
|
route_table = copy.deepcopy(fakes.DB_ROUTE_TABLE_1)
|
|
route_table['routes'].append({
|
|
'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_1,
|
|
'destination_cidr_block': '192.168.75.0/24'})
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': '192.168.75.0/24',
|
|
'NetworkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1},
|
|
route_table, fakes.DB_ROUTE_TABLE_1)
|
|
|
|
route_table = copy.deepcopy(fakes.DB_ROUTE_TABLE_1)
|
|
route_table['routes'].append({
|
|
'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_2,
|
|
'destination_cidr_block': '192.168.80.0/24'})
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': '192.168.80.0/24',
|
|
'InstanceId': fakes.ID_EC2_INSTANCE_1},
|
|
route_table, fakes.DB_ROUTE_TABLE_1)
|
|
|
|
# NOTE(ft): check idempotent calls
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': fakes.CIDR_EXTERNAL_NETWORK,
|
|
'InstanceId': fakes.ID_EC2_INSTANCE_1},
|
|
fakes.DB_ROUTE_TABLE_2, fakes.DB_ROUTE_TABLE_2)
|
|
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '0.0.0.0/0',
|
|
'GatewayId': fakes.ID_EC2_IGW_1},
|
|
fakes.DB_ROUTE_TABLE_2, fakes.DB_ROUTE_TABLE_2)
|
|
|
|
def test_create_route_invalid_parameters(self):
|
|
id_ec2_eni_vpc_2 = fakes.random_ec2_id('eni')
|
|
eni_vpc_2 = fakes.gen_db_network_interface(
|
|
id_ec2_eni_vpc_2, fakes.random_os_id(),
|
|
fakes.ID_EC2_VPC_2, fakes.random_ec2_id('subnet'), '10.20.0.10',
|
|
instance_id=fakes.ID_EC2_INSTANCE_2)
|
|
|
|
eni_2_in_instance_1 = fakes.gen_db_network_interface(
|
|
fakes.random_ec2_id('eni'), fakes.random_os_id(),
|
|
fakes.ID_EC2_VPC_1, fakes.random_ec2_id('subnet'), '10.10.3.15',
|
|
instance_id=fakes.ID_EC2_INSTANCE_1)
|
|
|
|
self.set_mock_db_items(
|
|
fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_2,
|
|
fakes.DB_VPC_1, eni_vpc_2, fakes.DB_IGW_1, fakes.DB_IGW_2,
|
|
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2)
|
|
|
|
def do_check(params, error_code):
|
|
self.assert_execution_error(error_code, 'CreateRoute', params)
|
|
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': 'not_a_cidr',
|
|
'GatewayId': fakes.ID_EC2_IGW_1},
|
|
'InvalidParameterValue')
|
|
|
|
do_check({'RouteTableId': fakes.random_ec2_id('rtb'),
|
|
'DestinationCidrBlock': fakes.CIDR_VPC_1,
|
|
'GatewayId': fakes.ID_EC2_IGW_1},
|
|
'InvalidRouteTableID.NotFound')
|
|
|
|
# NOTE(ft): redefine vpc local route
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': fakes.CIDR_VPC_1,
|
|
'GatewayId': fakes.ID_EC2_IGW_1},
|
|
'InvalidParameterValue')
|
|
|
|
# NOTE(ft): create route for cidr lesser than vpc cidr
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': fakes.IP_NETWORK_INTERFACE_1 + '/24',
|
|
'GatewayId': fakes.ID_EC2_IGW_1},
|
|
'InvalidParameterValue')
|
|
|
|
# NOTE(ft): redefine existed route by route with another attributes
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '0.0.0.0/0',
|
|
'NetworkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1},
|
|
'RouteAlreadyExists')
|
|
|
|
# NOTE(ft): missed traffic receiver
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '0.0.0.0/0'},
|
|
'MissingParameter')
|
|
|
|
# NOTE(ft): more than one traffic receiver
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '0.0.0.0/0',
|
|
'NetworkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1,
|
|
'GatewayId': fakes.ID_EC2_IGW_1},
|
|
'InvalidParameterCombination')
|
|
|
|
# NOTE(ft): gateway from different vpc
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '192.168.100.0/0',
|
|
'GatewayId': fakes.ID_EC2_IGW_2},
|
|
'InvalidParameterValue')
|
|
|
|
# NOTE(ft): network interface from different vpc
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '192.168.100.0/0',
|
|
'NetworkInterfaceId': id_ec2_eni_vpc_2},
|
|
'InvalidParameterValue')
|
|
|
|
# NOTE(ft): not vpc instance
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '192.168.100.0/0',
|
|
'InstanceId': fakes.ID_EC2_INSTANCE_2},
|
|
'InvalidParameterValue')
|
|
|
|
# NOTE(ft): multiple network interfaces in instance
|
|
self.add_mock_db_items(eni_2_in_instance_1)
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '192.168.100.0/0',
|
|
'InstanceId': fakes.ID_EC2_INSTANCE_1},
|
|
'InvalidInstanceID')
|
|
|
|
# NOTE(ft): different vpc instance
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '192.168.100.0/0',
|
|
'InstanceId': fakes.ID_EC2_INSTANCE_2},
|
|
'InvalidParameterValue')
|
|
|
|
@mock.patch('ec2api.api.route_table._update_routes_in_associated_subnets')
|
|
def test_create_or_replace_route_rollback(self, routes_updater):
|
|
self.set_mock_db_items(
|
|
fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_2,
|
|
fakes.DB_VPC_1, fakes.DB_IGW_1,
|
|
fakes.gen_db_igw(fakes.ID_EC2_IGW_2, fakes.ID_EC2_VPC_1))
|
|
routes_updater.side_effect = Exception()
|
|
|
|
with tools.ScreeningLogger(log_name='ec2api.api'):
|
|
self.assert_execution_error(
|
|
self.ANY_EXECUTE_ERROR, 'CreateRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': '0.0.0.0/0',
|
|
'GatewayId': fakes.ID_EC2_IGW_1})
|
|
|
|
self.db_api.update_item.assert_any_call(mock.ANY,
|
|
fakes.DB_ROUTE_TABLE_1)
|
|
|
|
with tools.ScreeningLogger(log_name='ec2api.api'):
|
|
self.assert_execution_error(
|
|
self.ANY_EXECUTE_ERROR, 'ReplaceRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '0.0.0.0/0',
|
|
'GatewayId': fakes.ID_EC2_IGW_2})
|
|
|
|
self.db_api.update_item.assert_any_call(mock.ANY,
|
|
fakes.DB_ROUTE_TABLE_2)
|
|
|
|
@mock.patch('ec2api.api.route_table._update_routes_in_associated_subnets')
|
|
def test_replace_route(self, routes_updater):
|
|
route_table = copy.deepcopy(fakes.DB_ROUTE_TABLE_1)
|
|
route_table['routes'].append({'gateway_id': fakes.ID_EC2_IGW_1,
|
|
'destination_cidr_block': '0.0.0.0/0'})
|
|
self.set_mock_db_items(
|
|
route_table, fakes.DB_VPC_1, fakes.DB_IGW_1,
|
|
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2)
|
|
|
|
resp = self.execute('ReplaceRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': '0.0.0.0/0',
|
|
'NetworkInterfaceId':
|
|
fakes.ID_EC2_NETWORK_INTERFACE_1})
|
|
self.assertEqual(True, resp['return'])
|
|
|
|
rollback_route_table_state = route_table
|
|
route_table = copy.deepcopy(fakes.DB_ROUTE_TABLE_1)
|
|
route_table['routes'].append({
|
|
'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_1,
|
|
'destination_cidr_block': '0.0.0.0/0'})
|
|
self.db_api.update_item.assert_called_once_with(mock.ANY, route_table)
|
|
routes_updater.assert_called_once_with(mock.ANY, route_table, mock.ANY,
|
|
rollback_route_table_state)
|
|
|
|
def test_replace_route_invalid_parameters(self):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1,
|
|
fakes.DB_VPC_1, fakes.DB_IGW_1)
|
|
|
|
self.assert_execution_error(
|
|
'InvalidParameterValue', 'ReplaceRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'DestinationCidrBlock': '11.22.33.0/24',
|
|
'GatewayId': fakes.ID_EC2_IGW_1})
|
|
|
|
@mock.patch('ec2api.api.route_table._update_routes_in_associated_subnets')
|
|
def test_delete_route(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2)
|
|
resp = self.execute('DeleteRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock':
|
|
fakes.CIDR_EXTERNAL_NETWORK})
|
|
self.assertEqual(True, resp['return'])
|
|
route_table = copy.deepcopy(fakes.DB_ROUTE_TABLE_2)
|
|
route_table['routes'] = [
|
|
r for r in route_table['routes']
|
|
if r['destination_cidr_block'] != fakes.CIDR_EXTERNAL_NETWORK]
|
|
self.db_api.update_item.assert_called_once_with(mock.ANY, route_table)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, route_table, mock.ANY, fakes.DB_ROUTE_TABLE_2)
|
|
|
|
def test_delete_route_invalid_parameters(self):
|
|
self.set_mock_db_items()
|
|
self.assert_execution_error(
|
|
'InvalidRouteTableID.NotFound', 'DeleteRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '11.22.33.0/24'})
|
|
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2)
|
|
self.assert_execution_error(
|
|
'InvalidRoute.NotFound', 'DeleteRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': '11.22.33.0/24'})
|
|
|
|
self.assert_execution_error(
|
|
'InvalidParameterValue', 'DeleteRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': fakes.CIDR_VPC_1})
|
|
|
|
@tools.screen_unexpected_exception_logs
|
|
@mock.patch('ec2api.api.route_table._update_routes_in_associated_subnets')
|
|
def test_delete_route_rollback(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2)
|
|
routes_updater.side_effect = Exception()
|
|
|
|
self.assert_execution_error(
|
|
self.ANY_EXECUTE_ERROR, 'DeleteRoute',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'DestinationCidrBlock': fakes.CIDR_EXTERNAL_NETWORK})
|
|
|
|
self.db_api.update_item.assert_any_call(
|
|
mock.ANY, fakes.DB_ROUTE_TABLE_2)
|
|
|
|
@mock.patch('ec2api.api.route_table._update_subnet_host_routes')
|
|
def test_associate_route_table(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_VPC_1, fakes.DB_ROUTE_TABLE_1,
|
|
fakes.DB_SUBNET_1)
|
|
resp = self.execute('AssociateRouteTable',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'SubnetId': fakes.ID_EC2_SUBNET_1})
|
|
self.assertEqual(fakes.ID_EC2_SUBNET_1.replace('subnet', 'rtbassoc'),
|
|
resp['associationId'])
|
|
subnet = tools.update_dict(
|
|
fakes.DB_SUBNET_1,
|
|
{'route_table_id': fakes.ID_EC2_ROUTE_TABLE_1})
|
|
self.db_api.update_item.assert_called_once_with(
|
|
mock.ANY, subnet)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, subnet, fakes.DB_ROUTE_TABLE_1, cleaner=mock.ANY,
|
|
rollback_route_table_object=fakes.DB_ROUTE_TABLE_1)
|
|
|
|
def test_associate_route_table_invalid_parameters(self):
|
|
def do_check(params, error_code):
|
|
self.assert_execution_error(
|
|
error_code, 'AssociateRouteTable', params)
|
|
|
|
self.set_mock_db_items()
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'SubnetId': fakes.ID_EC2_SUBNET_1},
|
|
'InvalidRouteTableID.NotFound')
|
|
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1)
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'SubnetId': fakes.ID_EC2_SUBNET_1},
|
|
'InvalidSubnetID.NotFound')
|
|
|
|
id_ec2_subnet_vpc_2 = fakes.random_ec2_id('subnet')
|
|
db_subnet_vpc_2 = {'id': id_ec2_subnet_vpc_2,
|
|
'os_id': fakes.random_os_id(),
|
|
'vpc_id': fakes.ID_EC2_VPC_2}
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, db_subnet_vpc_2)
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'SubnetId': id_ec2_subnet_vpc_2},
|
|
'InvalidParameterValue')
|
|
|
|
subnet_2 = tools.update_dict(
|
|
fakes.DB_SUBNET_2,
|
|
{'route_table_id': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, subnet_2)
|
|
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'SubnetId': fakes.ID_EC2_SUBNET_2},
|
|
'Resource.AlreadyAssociated')
|
|
|
|
@tools.screen_unexpected_exception_logs
|
|
@mock.patch('ec2api.api.route_table._update_subnet_host_routes')
|
|
def test_associate_route_table_rollback(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_VPC_1, fakes.DB_ROUTE_TABLE_1,
|
|
fakes.DB_SUBNET_1)
|
|
routes_updater.side_effect = Exception()
|
|
|
|
self.assert_execution_error(
|
|
self.ANY_EXECUTE_ERROR, 'AssociateRouteTable',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'SubnetId': fakes.ID_EC2_SUBNET_1})
|
|
|
|
self.db_api.update_item.assert_any_call(mock.ANY, fakes.DB_SUBNET_1)
|
|
|
|
@mock.patch('ec2api.api.route_table._update_subnet_host_routes')
|
|
def test_replace_route_table_association(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2, fakes.DB_ROUTE_TABLE_3,
|
|
fakes.DB_SUBNET_2)
|
|
resp = self.execute(
|
|
'ReplaceRouteTableAssociation',
|
|
{'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_3,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
self.assertEqual(fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_2,
|
|
resp['newAssociationId'])
|
|
subnet = tools.update_dict(
|
|
fakes.DB_SUBNET_2,
|
|
{'route_table_id': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
self.db_api.update_item.assert_called_once_with(
|
|
mock.ANY, subnet)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, subnet, fakes.DB_ROUTE_TABLE_2, cleaner=mock.ANY,
|
|
rollback_route_table_object=fakes.DB_ROUTE_TABLE_3)
|
|
|
|
@mock.patch('ec2api.api.route_table._update_routes_in_associated_subnets')
|
|
def test_replace_route_table_association_main(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_2,
|
|
fakes.DB_VPC_1)
|
|
resp = self.execute('ReplaceRouteTableAssociation',
|
|
{'AssociationId':
|
|
fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
self.assertEqual(fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1,
|
|
resp['newAssociationId'])
|
|
vpc = tools.update_dict(
|
|
fakes.DB_VPC_1,
|
|
{'route_table_id': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
self.db_api.update_item.assert_called_once_with(
|
|
mock.ANY, vpc)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, fakes.DB_ROUTE_TABLE_2, mock.ANY,
|
|
fakes.DB_ROUTE_TABLE_1, is_main=True)
|
|
|
|
def test_replace_route_table_association_invalid_parameters(self):
|
|
def do_check(params, error_code):
|
|
self.assert_execution_error(
|
|
error_code, 'ReplaceRouteTableAssociation', params)
|
|
|
|
self.set_mock_db_items()
|
|
do_check({'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1},
|
|
'InvalidRouteTableID.NotFound')
|
|
|
|
# NOTE(ft): association with vpc is obsolete
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1)
|
|
do_check({'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1},
|
|
'InvalidAssociationID.NotFound')
|
|
|
|
# NOTE(ft): association with subnet is obsolete (no subnet)
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_3)
|
|
do_check({'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_3,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_3},
|
|
'InvalidAssociationID.NotFound')
|
|
|
|
# NOTE(ft): association with subnet is obsolete (subnet is
|
|
# disassociated)
|
|
self.set_mock_db_items(
|
|
fakes.DB_ROUTE_TABLE_3,
|
|
tools.purge_dict(fakes.DB_SUBNET_2, ['route_table_id']))
|
|
do_check({'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_3,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_3},
|
|
'InvalidAssociationID.NotFound')
|
|
|
|
# NOTE(ft): association belongs to different vpc
|
|
id_ec2_subnet_vpc_2 = fakes.random_ec2_id('subnet')
|
|
db_subnet_vpc_2 = {'id': id_ec2_subnet_vpc_2,
|
|
'os_id': fakes.random_os_id(),
|
|
'vpc_id': fakes.ID_EC2_VPC_2,
|
|
'route_table_id': fakes.random_ec2_id('rtb')}
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2, db_subnet_vpc_2)
|
|
do_check({'AssociationId': ec2utils.change_ec2_id_kind(
|
|
id_ec2_subnet_vpc_2, 'rtbassoc'),
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2},
|
|
'InvalidParameterValue')
|
|
|
|
@mock.patch('ec2api.api.route_table._update_routes_in_associated_subnets')
|
|
@mock.patch('ec2api.api.route_table._update_subnet_host_routes')
|
|
def test_replace_route_table_association_rollback(self, routes_updater,
|
|
multiply_routes_updater):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_2,
|
|
fakes.DB_ROUTE_TABLE_3, fakes.DB_SUBNET_2,
|
|
fakes.DB_VPC_1)
|
|
multiply_routes_updater.side_effect = Exception()
|
|
|
|
with tools.ScreeningLogger(log_name='ec2api.api'):
|
|
self.assert_execution_error(
|
|
self.ANY_EXECUTE_ERROR, 'ReplaceRouteTableAssociation',
|
|
{'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
|
|
self.db_api.update_item.assert_any_call(
|
|
mock.ANY, fakes.DB_VPC_1)
|
|
|
|
self.db_api.reset_mock()
|
|
routes_updater.side_effect = Exception()
|
|
|
|
with tools.ScreeningLogger(log_name='ec2api.api'):
|
|
self.assert_execution_error(
|
|
self.ANY_EXECUTE_ERROR, 'ReplaceRouteTableAssociation',
|
|
{'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_3,
|
|
'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
|
|
self.db_api.update_item.assert_any_call(
|
|
mock.ANY, fakes.DB_SUBNET_2)
|
|
|
|
@mock.patch('ec2api.api.route_table._update_subnet_host_routes')
|
|
def test_disassociate_route_table(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_3,
|
|
fakes.DB_SUBNET_2, fakes.DB_VPC_1)
|
|
resp = self.execute(
|
|
'DisassociateRouteTable',
|
|
{'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_3})
|
|
self.assertEqual(True, resp['return'])
|
|
subnet = tools.purge_dict(fakes.DB_SUBNET_2, ('route_table_id',))
|
|
self.db_api.update_item.assert_called_once_with(
|
|
mock.ANY, subnet)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, subnet, fakes.DB_ROUTE_TABLE_1,
|
|
cleaner=mock.ANY,
|
|
rollback_route_table_object=fakes.DB_ROUTE_TABLE_3)
|
|
|
|
def test_disassociate_route_table_invalid_parameter(self):
|
|
def do_check(params, error_code):
|
|
self.assert_execution_error(
|
|
error_code, 'DisassociateRouteTable', params)
|
|
|
|
self.set_mock_db_items()
|
|
do_check({'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1},
|
|
'InvalidAssociationID.NotFound')
|
|
|
|
self.set_mock_db_items(
|
|
tools.purge_dict(fakes.DB_SUBNET_1, ['route_table_id']))
|
|
do_check({'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_2},
|
|
'InvalidAssociationID.NotFound')
|
|
|
|
self.set_mock_db_items(fakes.DB_VPC_1)
|
|
do_check({'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1},
|
|
'InvalidParameterValue')
|
|
|
|
@tools.screen_unexpected_exception_logs
|
|
@mock.patch('ec2api.api.route_table._update_subnet_host_routes')
|
|
def test_disassociate_route_table_rollback(self, routes_updater):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_3,
|
|
fakes.DB_SUBNET_2, fakes.DB_VPC_1)
|
|
routes_updater.side_effect = Exception()
|
|
|
|
self.assert_execution_error(
|
|
self.ANY_EXECUTE_ERROR, 'DisassociateRouteTable',
|
|
{'AssociationId': fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_3})
|
|
|
|
self.db_api.update_item.assert_any_call(
|
|
mock.ANY, fakes.DB_SUBNET_2)
|
|
|
|
def test_delete_route_table(self):
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2, fakes.DB_VPC_1,
|
|
fakes.DB_SUBNET_1, fakes.DB_SUBNET_2)
|
|
resp = self.execute('DeleteRouteTable',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
self.assertEqual(True, resp['return'])
|
|
self.db_api.delete_item.assert_called_once_with(
|
|
mock.ANY,
|
|
fakes.ID_EC2_ROUTE_TABLE_2)
|
|
|
|
def test_delete_route_table_invalid_parameters(self):
|
|
self.set_mock_db_items()
|
|
self.assert_execution_error(
|
|
'InvalidRouteTableID.NotFound', 'DeleteRouteTable',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1})
|
|
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, fakes.DB_VPC_1)
|
|
self.assert_execution_error(
|
|
'DependencyViolation', 'DeleteRouteTable',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1})
|
|
|
|
subnet = tools.update_dict(
|
|
fakes.DB_SUBNET_2,
|
|
{'route_table_id': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2, fakes.DB_VPC_1, subnet)
|
|
self.assert_execution_error(
|
|
'DependencyViolation', 'DeleteRouteTable',
|
|
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
|
|
def test_describe_route_tables(self):
|
|
self.set_mock_db_items(
|
|
fakes.DB_ROUTE_TABLE_1, fakes.DB_ROUTE_TABLE_2,
|
|
fakes.DB_ROUTE_TABLE_3, fakes.DB_SUBNET_1, fakes.DB_SUBNET_2,
|
|
fakes.DB_VPC_1, fakes.DB_VPC_2, fakes.DB_IGW_1, fakes.DB_IGW_2,
|
|
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2,
|
|
fakes.DB_INSTANCE_1)
|
|
self.nova.servers.get.return_value = (
|
|
mock.NonCallableMock(status='ACTIVE'))
|
|
|
|
resp = self.execute('DescribeRouteTables', {})
|
|
self.assertThat(resp['routeTableSet'],
|
|
matchers.ListMatches([fakes.EC2_ROUTE_TABLE_1,
|
|
fakes.EC2_ROUTE_TABLE_2,
|
|
fakes.EC2_ROUTE_TABLE_3]))
|
|
|
|
resp = self.execute('DescribeRouteTables',
|
|
{'RouteTableId.1': fakes.ID_EC2_ROUTE_TABLE_1})
|
|
self.assertThat(resp['routeTableSet'],
|
|
matchers.ListMatches([fakes.EC2_ROUTE_TABLE_1]))
|
|
self.db_api.get_items_by_ids.assert_called_once_with(
|
|
mock.ANY, set([fakes.ID_EC2_ROUTE_TABLE_1]))
|
|
|
|
self.check_filtering(
|
|
'DescribeRouteTables', 'routeTableSet',
|
|
[('association.route-table-association-id',
|
|
fakes.ID_EC2_ROUTE_TABLE_ASSOCIATION_1),
|
|
('association.route-table-id', fakes.ID_EC2_ROUTE_TABLE_1),
|
|
('association.subnet-id', fakes.ID_EC2_SUBNET_2),
|
|
# TODO(ft): support filtering by a boolean value
|
|
# ('association.main', True),
|
|
('route-table-id', fakes.ID_EC2_ROUTE_TABLE_1),
|
|
('route.destination-cidr-block', fakes.CIDR_EXTERNAL_NETWORK),
|
|
('route.gateway-id', 'local'),
|
|
('route.instance-id', fakes.ID_EC2_INSTANCE_1),
|
|
('route.origin', 'CreateRouteTable'),
|
|
('route.state', 'active'),
|
|
('vpc-id', fakes.ID_EC2_VPC_1)])
|
|
self.check_tag_support(
|
|
'DescribeRouteTables', 'routeTableSet',
|
|
fakes.ID_EC2_ROUTE_TABLE_1, 'routeTableId')
|
|
|
|
def test_describe_route_tables_variations(self):
|
|
igw_1 = tools.purge_dict(fakes.DB_IGW_1, ('vpc_id',))
|
|
igw_2 = tools.update_dict(fakes.DB_IGW_2,
|
|
{'vpc_id': fakes.ID_EC2_VPC_2})
|
|
subnet_1 = tools.update_dict(
|
|
fakes.DB_SUBNET_1,
|
|
{'route_table_id': fakes.ID_EC2_ROUTE_TABLE_1})
|
|
subnet_2 = tools.update_dict(
|
|
fakes.DB_SUBNET_2,
|
|
{'route_table_id': fakes.ID_EC2_ROUTE_TABLE_2})
|
|
route_table_1 = copy.deepcopy(fakes.DB_ROUTE_TABLE_1)
|
|
route_table_1['routes'].append(
|
|
{'destination_cidr_block': '0.0.0.0/0',
|
|
'gateway_id': fakes.ID_EC2_IGW_2})
|
|
route_table_1['routes'].append(
|
|
{'destination_cidr_block': '192.168.77.0/24',
|
|
'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_1})
|
|
deleted_eni_id = fakes.random_ec2_id('eni')
|
|
route_table_1['routes'].append(
|
|
{'destination_cidr_block': '192.168.99.0/24',
|
|
'network_interface_id': deleted_eni_id})
|
|
route_table_2 = copy.deepcopy(fakes.DB_ROUTE_TABLE_2)
|
|
route_table_2['routes'].append(
|
|
{'destination_cidr_block': '192.168.88.0/24',
|
|
'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_2})
|
|
self.set_mock_db_items(
|
|
route_table_1, route_table_2, fakes.DB_VPC_1, fakes.DB_VPC_2,
|
|
igw_1, igw_2, subnet_1, subnet_2,
|
|
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2)
|
|
self.nova.servers.get.return_value = (
|
|
mock.NonCallableMock(status='DOWN'))
|
|
resp = self.execute('DescribeRouteTables', {})
|
|
ec2_route_table_1 = copy.deepcopy(fakes.EC2_ROUTE_TABLE_1)
|
|
ec2_route_table_1['routeSet'].append({
|
|
'destinationCidrBlock': '0.0.0.0/0',
|
|
'gatewayId': fakes.ID_EC2_IGW_2,
|
|
'state': 'blackhole',
|
|
'origin': 'CreateRoute'})
|
|
ec2_route_table_1['routeSet'].append({
|
|
'destinationCidrBlock': '192.168.77.0/24',
|
|
'networkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1,
|
|
'state': 'blackhole',
|
|
'origin': 'CreateRoute'})
|
|
ec2_route_table_1['routeSet'].append({
|
|
'destinationCidrBlock': '192.168.99.0/24',
|
|
'networkInterfaceId': deleted_eni_id,
|
|
'state': 'blackhole',
|
|
'origin': 'CreateRoute'})
|
|
ec2_route_table_1['associationSet'].append({
|
|
'routeTableAssociationId':
|
|
fakes.ID_EC2_SUBNET_1.replace('subnet', 'rtbassoc'),
|
|
'routeTableId': fakes.ID_EC2_ROUTE_TABLE_1,
|
|
'subnetId': fakes.ID_EC2_SUBNET_1,
|
|
'main': False})
|
|
ec2_route_table_2 = copy.deepcopy(fakes.EC2_ROUTE_TABLE_2)
|
|
ec2_route_table_2['routeSet'][1]['state'] = 'blackhole'
|
|
ec2_route_table_2['routeSet'][2]['state'] = 'blackhole'
|
|
ec2_route_table_2['routeSet'].append({
|
|
'destinationCidrBlock': '192.168.88.0/24',
|
|
'networkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_2,
|
|
'instanceId': fakes.ID_EC2_INSTANCE_1,
|
|
'instanceOwnerId': fakes.ID_OS_PROJECT,
|
|
'state': 'blackhole',
|
|
'origin': 'CreateRoute'})
|
|
ec2_route_table_2['associationSet'] = [{
|
|
'routeTableAssociationId':
|
|
fakes.ID_EC2_SUBNET_2.replace('subnet', 'rtbassoc'),
|
|
'routeTableId': fakes.ID_EC2_ROUTE_TABLE_2,
|
|
'subnetId': fakes.ID_EC2_SUBNET_2,
|
|
'main': False}]
|
|
self.assertThat(resp['routeTableSet'],
|
|
matchers.ListMatches([ec2_route_table_1,
|
|
ec2_route_table_2]))
|
|
|
|
def test_get_subnet_host_routes(self):
|
|
self.set_mock_db_items(
|
|
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2,
|
|
fakes.DB_IGW_1)
|
|
|
|
host_routes = route_table._get_subnet_host_routes(
|
|
mock.ANY, fakes.DB_ROUTE_TABLE_1, fakes.IP_GATEWAY_SUBNET_1)
|
|
|
|
self.assertThat(host_routes,
|
|
matchers.ListMatches([
|
|
{'destination': fakes.CIDR_VPC_1,
|
|
'nexthop': fakes.IP_GATEWAY_SUBNET_1},
|
|
{'destination': '0.0.0.0/0',
|
|
'nexthop': '127.0.0.1'}]))
|
|
|
|
host_routes = route_table._get_subnet_host_routes(
|
|
mock.ANY, fakes.DB_ROUTE_TABLE_2, fakes.IP_GATEWAY_SUBNET_1)
|
|
|
|
self.assertThat(host_routes,
|
|
matchers.ListMatches([
|
|
{'destination': fakes.CIDR_VPC_1,
|
|
'nexthop': fakes.IP_GATEWAY_SUBNET_1},
|
|
{'destination': fakes.CIDR_EXTERNAL_NETWORK,
|
|
'nexthop': fakes.IP_NETWORK_INTERFACE_2},
|
|
{'destination': '0.0.0.0/0',
|
|
'nexthop': fakes.IP_GATEWAY_SUBNET_1}]))
|
|
|
|
@mock.patch('ec2api.api.route_table._get_subnet_host_routes')
|
|
def test_update_subnet_host_routes(self, routes_getter):
|
|
self.neutron.show_subnet.return_value = {'subnet': fakes.OS_SUBNET_1}
|
|
routes_getter.return_value = 'fake_routes'
|
|
|
|
route_table._update_subnet_host_routes(
|
|
self._create_context(), fakes.DB_SUBNET_1,
|
|
fakes.DB_ROUTE_TABLE_1, router_objects={'fake': 'objects'})
|
|
|
|
self.neutron.show_subnet.assert_called_once_with(fakes.ID_OS_SUBNET_1)
|
|
routes_getter.assert_called_once_with(
|
|
mock.ANY, fakes.DB_ROUTE_TABLE_1, fakes.IP_GATEWAY_SUBNET_1,
|
|
{'fake': 'objects'})
|
|
self.neutron.update_subnet.assert_called_once_with(
|
|
fakes.ID_OS_SUBNET_1,
|
|
{'subnet': {'host_routes': 'fake_routes'}})
|
|
|
|
self.neutron.reset_mock()
|
|
routes_getter.reset_mock()
|
|
|
|
routes_getter.side_effect = ['fake_routes', 'fake_previous_routes']
|
|
|
|
try:
|
|
with common.OnCrashCleaner() as cleaner:
|
|
route_table._update_subnet_host_routes(
|
|
self._create_context(), fakes.DB_SUBNET_1,
|
|
fakes.DB_ROUTE_TABLE_1, cleaner,
|
|
fakes.DB_ROUTE_TABLE_2,
|
|
router_objects={'fake': 'objects'})
|
|
raise Exception('fake_exception')
|
|
except Exception as ex:
|
|
if ex.message != 'fake_exception':
|
|
raise
|
|
|
|
self.neutron.show_subnet.assert_any_call(fakes.ID_OS_SUBNET_1)
|
|
routes_getter.assert_any_call(
|
|
mock.ANY, fakes.DB_ROUTE_TABLE_1, fakes.IP_GATEWAY_SUBNET_1,
|
|
{'fake': 'objects'})
|
|
routes_getter.assert_any_call(
|
|
mock.ANY, fakes.DB_ROUTE_TABLE_2, fakes.IP_GATEWAY_SUBNET_1,
|
|
None)
|
|
self.neutron.update_subnet.assert_any_call(
|
|
fakes.ID_OS_SUBNET_1,
|
|
{'subnet': {'host_routes': 'fake_previous_routes'}})
|
|
|
|
@mock.patch('ec2api.api.route_table._get_router_objects')
|
|
@mock.patch('ec2api.api.route_table._update_subnet_host_routes')
|
|
def test_update_routes_in_associated_subnets(self, routes_updater,
|
|
get_router_objects):
|
|
subnet_default_rtb = {'id': 'fake_1',
|
|
'vpc_id': fakes.ID_EC2_VPC_1}
|
|
subnet_rtb_2 = {'id': 'fake_2',
|
|
'vpc_id': fakes.ID_EC2_VPC_1,
|
|
'route_table_id': fakes.ID_EC2_ROUTE_TABLE_2}
|
|
subnet_vpc_2 = {'id': 'fake_3',
|
|
'vpc_id': fakes.ID_EC2_VPC_2}
|
|
self.db_api.get_items.return_value = [subnet_default_rtb,
|
|
subnet_rtb_2, subnet_vpc_2]
|
|
self.db_api.get_item_by_id.return_value = fakes.DB_VPC_1
|
|
get_router_objects.return_value = {'fake': 'objects'}
|
|
|
|
route_table._update_routes_in_associated_subnets(
|
|
mock.MagicMock(), fakes.DB_ROUTE_TABLE_2, 'fake_cleaner',
|
|
{'fake': 'table'})
|
|
|
|
self.db_api.get_item_by_id.assert_called_once_with(
|
|
mock.ANY, fakes.ID_EC2_VPC_1)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, subnet_rtb_2, fakes.DB_ROUTE_TABLE_2,
|
|
cleaner='fake_cleaner',
|
|
rollback_route_table_object={'fake': 'table'},
|
|
router_objects={'fake': 'objects'}, neutron=mock.ANY)
|
|
get_router_objects.assert_called_once_with(mock.ANY,
|
|
fakes.DB_ROUTE_TABLE_2)
|
|
|
|
self.db_api.get_item_by_id.reset_mock()
|
|
routes_updater.reset_mock()
|
|
get_router_objects.reset_mock()
|
|
|
|
route_table._update_routes_in_associated_subnets(
|
|
mock.MagicMock(), fakes.DB_ROUTE_TABLE_1, 'fake_cleaner',
|
|
{'fake': 'table'}, is_main=True)
|
|
|
|
self.assertEqual(0, self.db_api.get_item_by_id.call_count)
|
|
routes_updater.assert_called_once_with(
|
|
mock.ANY, subnet_default_rtb, fakes.DB_ROUTE_TABLE_1,
|
|
cleaner='fake_cleaner',
|
|
rollback_route_table_object={'fake': 'table'},
|
|
router_objects={'fake': 'objects'}, neutron=mock.ANY)
|
|
get_router_objects.assert_called_once_with(mock.ANY,
|
|
fakes.DB_ROUTE_TABLE_1)
|
|
|
|
def test_get_router_objects(self):
|
|
self.set_mock_db_items(fakes.DB_IGW_1, fakes.DB_NETWORK_INTERFACE_2)
|
|
host_routes = route_table._get_router_objects('fake_context',
|
|
fakes.DB_ROUTE_TABLE_2)
|
|
self.assertThat(host_routes, matchers.DictMatches({
|
|
fakes.ID_EC2_IGW_1: fakes.DB_IGW_1,
|
|
fakes.ID_EC2_NETWORK_INTERFACE_2:
|
|
fakes.DB_NETWORK_INTERFACE_2}))
|