Reworked validation mechanism once more

Change-Id: I2d833fc4c3a1d4178075354eddccfe9f195d3e3a
This commit is contained in:
alevine 2014-12-30 14:02:31 +04:00
parent cf63edf9f9
commit 6671fcdd57
12 changed files with 196 additions and 159 deletions

View File

@ -332,92 +332,6 @@ class Requestify(wsgi.Middleware):
class Validator(wsgi.Middleware): class Validator(wsgi.Middleware):
validator.DEFAULT_VALIDATOR = { validator.DEFAULT_VALIDATOR = {
'AllocationId': validator.validate_ec2_id(['eipalloc']),
'AllowReassignment': validator.validate_dummy,
'AllowReassociation': validator.validate_dummy,
'Architecture': validator.validate_dummy,
'AssociationId': validator.validate_ec2_association_id,
'AttachmentId': validator.validate_ec2_id(['eni-attach']),
'Attribute': validator.validate_dummy,
'AvailabilityZone': validator.validate_dummy,
'BlockDeviceMapping': validator.validate_dummy,
'CidrBlock': validator.validate_cidr_block,
'ClientToken': validator.validate_dummy,
'Description': validator.validate_dummy,
'DestinationCidrBlock': validator.validate_cidr_block,
'Device': validator.validate_dummy,
'DeviceIndex': validator.validate_dummy,
'DhcpConfiguration': validator.validate_dummy,
'Dhcp_optionsId': validator.validate_dummy,
'DisableApiTermination': validator.validate_dummy,
'Domain': validator.validate_dummy,
'Ebs_optimized': validator.validate_dummy,
'Encrypted': validator.validate_dummy,
'ExecutableBy': validator.validate_dummy,
'Filter': validator.validate_dummy,
'Force': validator.validate_dummy,
'GatewayId': validator.validate_dummy,
'GroupDescription': validator.validate_str(max_length=255),
'GroupId': validator.validate_ec2_id(['sg']),
'GroupName': validator.validate_str(max_length=255),
'IamInstanceProfile': validator.validate_dummy,
'ImageId': validator.validate_ec2_id(['ami', 'ari', 'aki']),
'ImageLocation': validator.validate_image_path,
'InstanceId': validator.validate_dummy,
'InstanceInitiatedShutdownBehavior': validator.validate_dummy,
'InstanceTenancy': validator.validate_dummy,
'InstanceType': validator.validate_dummy,
'InternetGatewayId': validator.validate_dummy,
'Iops': validator.validate_dummy,
'IpPermissions': validator.validate_dummy,
'KernelId': validator.validate_dummy,
'KeyName': validator.validate_dummy,
'KmsKeyId': validator.validate_dummy,
'LaunchPermission': validator.validate_dummy,
'MaxCount': validator.validate_dummy,
'MaxResults': validator.validate_dummy,
'Metadata': validator.validate_dummy,
'MinCount': validator.validate_dummy,
'Monitoring': validator.validate_dummy,
'Name': validator.validate_dummy,
'NetworkInterface': validator.validate_dummy,
'NetworkInterfaceId': validator.validate_dummy,
'NextToken': validator.validate_dummy,
'NoReboot': validator.validate_dummy,
'OperationType': validator.validate_dummy,
'Owner': validator.validate_dummy,
'Placement': validator.validate_dummy,
'PrivateIpAddress': validator.validate_dummy,
'PrivateIpAddresses': validator.validate_dummy,
'ProductCode': validator.validate_dummy,
'PublicIp': validator.validate_ipv4,
'PublicKey_material': validator.validate_dummy,
'RamdiskId': validator.validate_dummy,
'RemoteIpPrefix': validator.validate_dummy,
'RegionName': validator.validate_str(),
'ResourceId': validator.validate_dummy,
'RestorableBy': validator.validate_dummy,
'RootDeviceName': validator.validate_dummy,
'RouteTableId': validator.validate_dummy,
'SecondaryPrivateIpAddressCount': validator.validate_dummy,
'SecurityGroup': validator.validate_dummy,
'SecurityGroupId': validator.validate_dummy,
'Size': validator.validate_int(),
'SnapshotId': validator.validate_dummy,
'SourceDestCheck': validator.validate_dummy,
'SriovNetSupport': validator.validate_dummy,
'SubnetId': validator.validate_dummy,
'Tag': validator.validate_dummy,
'UserData': validator.validate_user_data,
'UserGroup': validator.validate_dummy,
'UserId': validator.validate_dummy,
'Value': validator.validate_dummy,
'VirtualizationType': validator.validate_dummy,
'VolumeId': validator.validate_dummy,
'VolumeType': validator.validate_dummy,
'VpcId': validator.validate_dummy,
'VpcPeeringConnectionId': validator.validate_dummy,
'ZoneName': validator.validate_dummy,
} }
def __init__(self, application): def __init__(self, application):

View File

@ -19,6 +19,8 @@ dispatched to other nodes via AMQP RPC. State is via distributed
datastore. datastore.
""" """
import itertools
from oslo.config import cfg from oslo.config import cfg
from ec2api.api import address from ec2api.api import address
@ -42,6 +44,29 @@ CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def module_and_param_types(module, *args, **kwargs):
"""Decorator to check types and call function."""
param_types = args
def wrapped(func):
def func_wrapped(*args, **kwargs):
impl_func = getattr(module, func.func_name)
context = args[1]
for param_name, param_type in itertools.izip(
func.func_code.co_varnames[2:], param_types):
param_value = kwargs.get(param_name)
if param_value:
validator = module.Validator(param_name, func.func_name)
validation_func = getattr(validator, param_type)
is_valid = validation_func(param_value)
return impl_func(context, **kwargs)
return func_wrapped
return wrapped
class CloudController(object): class CloudController(object):
"""Cloud Controller """Cloud Controller
@ -305,6 +330,16 @@ class CloudController(object):
context, group_id, context, group_id,
group_name, ip_permissions) group_name, ip_permissions)
@module_and_param_types(instance, 'ami_id', 'dummy', 'dummy',
'str255', 'sg_ids',
'str255s', 'dummy', 'dummy',
'dummy', 'ami_id', 'ami_id',
'dummy', 'dummy',
'subnet_id', 'dummy',
'dummy',
'dummy', 'dummy',
'dummy', 'dummy',
'dummy')
def run_instances(self, context, image_id, min_count, max_count, def run_instances(self, context, image_id, min_count, max_count,
key_name=None, security_group_id=None, key_name=None, security_group_id=None,
security_group=None, user_data=None, instance_type=None, security_group=None, user_data=None, instance_type=None,
@ -418,17 +453,8 @@ class CloudController(object):
If you don't specify a security group when launching an instance, EC2 If you don't specify a security group when launching an instance, EC2
uses the default security group. uses the default security group.
""" """
return instance.run_instances(context, image_id, min_count, max_count,
key_name, security_group_id,
security_group, user_data, instance_type,
placement, kernel_id, ramdisk_id,
block_device_mapping, monitoring,
subnet_id, disable_api_termination,
instance_initiated_shutdown_behavior,
private_ip_address, client_token,
network_interface, iam_instance_profile,
ebs_optimized)
@module_and_param_types(instance, 'i_ids')
def terminate_instances(self, context, instance_id): def terminate_instances(self, context, instance_id):
"""Shuts down one or more instances. """Shuts down one or more instances.
@ -442,8 +468,8 @@ class CloudController(object):
This operation is idempotent; if you terminate an instance more than This operation is idempotent; if you terminate an instance more than
once, each call succeeds. once, each call succeeds.
""" """
return instance.terminate_instances(context, instance_id)
@module_and_param_types(instance, 'i_ids', 'dummy', 'dummy', 'dummy')
def describe_instances(self, context, instance_id=None, filter=None, def describe_instances(self, context, instance_id=None, filter=None,
max_results=None, next_token=None): max_results=None, next_token=None):
"""Describes one or more of your instances. """Describes one or more of your instances.
@ -467,9 +493,8 @@ class CloudController(object):
instance ID, you receive an error. If you specify an instance that you instance ID, you receive an error. If you specify an instance that you
don't own, we don't include it in the results. don't own, we don't include it in the results.
""" """
return instance.describe_instances(context, instance_id, filter,
max_results, next_token)
@module_and_param_types(instance, 'i_ids')
def reboot_instances(self, context, instance_id): def reboot_instances(self, context, instance_id):
"""Requests a reboot of one or more instances. """Requests a reboot of one or more instances.
@ -480,8 +505,8 @@ class CloudController(object):
Returns: Returns:
true if the request succeeds. true if the request succeeds.
""" """
return instance.reboot_instances(context, instance_id)
@module_and_param_types(instance, 'i_ids', 'dummy')
def stop_instances(self, context, instance_id, force=False): def stop_instances(self, context, instance_id, force=False):
"""Stops one or more instances. """Stops one or more instances.
@ -496,8 +521,8 @@ class CloudController(object):
Returns: Returns:
true if the request succeeds. true if the request succeeds.
""" """
return instance.stop_instances(context, instance_id, force)
@module_and_param_types(instance, 'i_ids')
def start_instances(self, context, instance_id): def start_instances(self, context, instance_id):
"""Starts one or more instances. """Starts one or more instances.
@ -508,8 +533,8 @@ class CloudController(object):
Returns: Returns:
true if the request succeeds. true if the request succeeds.
""" """
return instance.start_instances(context, instance_id)
@module_and_param_types(instance, 'i_id', 'dummy')
def describe_instance_attribute(self, context, instance_id, attribute): def describe_instance_attribute(self, context, instance_id, attribute):
"""Describes the specified attribute of the specified instance. """Describes the specified attribute of the specified instance.
@ -527,8 +552,6 @@ class CloudController(object):
Returns: Returns:
Specified attribute. Specified attribute.
""" """
return instance.describe_instance_attribute(context, instance_id,
attribute)
def describe_key_pairs(self, context, key_name=None, filter=None): def describe_key_pairs(self, context, key_name=None, filter=None):
return key_pair.describe_key_pairs(context, key_name, filter) return key_pair.describe_key_pairs(context, key_name, filter)
@ -950,6 +973,7 @@ class VpcCloudController(CloudController):
Adds full VPC functionality which requires Neutron to work. Adds full VPC functionality which requires Neutron to work.
""" """
@module_and_param_types(vpc, 'vpc_cidr', 'str255')
def create_vpc(self, context, cidr_block, instance_tenancy='default'): def create_vpc(self, context, cidr_block, instance_tenancy='default'):
"""Creates a VPC with the specified CIDR block. """Creates a VPC with the specified CIDR block.
@ -968,7 +992,6 @@ class VpcCloudController(CloudController):
The smallest VPC you can create uses a /28 netmask (16 IP addresses), The smallest VPC you can create uses a /28 netmask (16 IP addresses),
and the largest uses a /16 netmask. and the largest uses a /16 netmask.
""" """
return vpc.create_vpc(context, cidr_block, instance_tenancy)
def delete_vpc(self, context, vpc_id): def delete_vpc(self, context, vpc_id):
"""Deletes the specified VPC. """Deletes the specified VPC.
@ -1087,6 +1110,7 @@ class VpcCloudController(CloudController):
internet_gateway_id, internet_gateway_id,
filter) filter)
@module_and_param_types(subnet, 'vpc_id', 'subnet_cidr', 'str255')
def create_subnet(self, context, vpc_id, cidr_block, def create_subnet(self, context, vpc_id, cidr_block,
availability_zone=None): availability_zone=None):
"""Creates a subnet in an existing VPC. """Creates a subnet in an existing VPC.
@ -1114,8 +1138,6 @@ class VpcCloudController(CloudController):
If you add more than one subnet to a VPC, they're set up If you add more than one subnet to a VPC, they're set up
in a star topology with a logical router in the middle. in a star topology with a logical router in the middle.
""" """
return subnet.create_subnet(context, vpc_id,
cidr_block, availability_zone)
def delete_subnet(self, context, subnet_id): def delete_subnet(self, context, subnet_id):
"""Deletes the specified subnet. """Deletes the specified subnet.
@ -1163,6 +1185,8 @@ class VpcCloudController(CloudController):
""" """
return route_table.create_route_table(context, vpc_id) return route_table.create_route_table(context, vpc_id)
@module_and_param_types(route_table, 'rtb_id', 'cidr',
'igw_id', 'i_id', 'eni_id', 'dummy')
def create_route(self, context, route_table_id, destination_cidr_block, def create_route(self, context, route_table_id, destination_cidr_block,
gateway_id=None, instance_id=None, gateway_id=None, instance_id=None,
network_interface_id=None, network_interface_id=None,
@ -1191,10 +1215,6 @@ class VpcCloudController(CloudController):
gateway attached to the VPC, a VPC peering connection, or a NAT gateway attached to the VPC, a VPC peering connection, or a NAT
instance in the VPC. instance in the VPC.
""" """
return route_table.create_route(context, route_table_id,
destination_cidr_block, gateway_id,
instance_id, network_interface_id,
vpc_peering_connection_id)
def replace_route(self, context, route_table_id, destination_cidr_block, def replace_route(self, context, route_table_id, destination_cidr_block,
gateway_id=None, instance_id=None, gateway_id=None, instance_id=None,

View File

@ -18,6 +18,7 @@ import fnmatch
from oslo.config import cfg from oslo.config import cfg
from ec2api.api import ec2utils from ec2api.api import ec2utils
from ec2api.api import validator
from ec2api.db import api as db_api from ec2api.db import api as db_api
from ec2api import exception from ec2api import exception
@ -31,6 +32,88 @@ ec2_opts = [
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(ec2_opts) CONF.register_opts(ec2_opts)
class Validator(object):
def __init__(self, param_name="", action=""):
self.param_name = param_name
self.action = action
def dummy(self, value):
pass
def str255(self, value):
validator.validate_str(value, self.param_name, 255)
def multi(self, items, validation_func):
validator.validate_list(items, self.param_name)
for item in items:
validation_func(item)
def str255s(self, values):
self.multi(values, self.str_255)
def cidr(self, cidr):
validator.validate_cidr(cidr, self.param_name)
def subnet_cidr(self, cidr):
validator.validate_subnet_cidr(cidr)
def vpc_cidr(self, cidr):
validator.validate_vpc_cidr(cidr)
def ec2_id(self, id, prefices):
validator.validate_ec2_id(id, self.param_name, prefices)
def i_id(self, id):
self.ec2_id(id, ['i'])
def i_ids(self, ids):
self.multi(ids, self.i_id)
def ami_id(self, id):
self.ec2_id(id, ['ami', 'ari', 'aki'])
def ami_ids(self, ids):
self.multi(ids, self.aki_id)
def sg_id(self, id):
self.ec2_id(id, ['sg'])
def sg_ids(self, ids):
self.multi(ids, self.sg_id)
def subnet_id(self, id):
self.ec2_id(id, ['subnet'])
def subnet_ids(self, ids):
self.multi(ids, self.subnet_id)
def igw_id(self, id):
self.ec2_id(id, ['igw'])
def igw_ids(self, ids):
self.multi(ids, self.igw_id)
def rtb_id(self, id):
self.ec2_id(id, ['rtb'])
def rtb_ids(self, ids):
self.multi(ids, self.rtb_id)
def eni_id(self, id):
self.ec2_id(id, ['eni'])
def eni_ids(self, ids):
self.multi(ids, self.eni_id)
def vpc_id(self, id):
self.ec2_id(id, ['vpc'])
def vpc_ids(self, ids):
self.multi(ids, self.vpc_id)
VPC_KINDS = ['vpc', 'igw', 'subnet', 'eni', 'dopt', 'eipalloc', 'sg', 'rtb'] VPC_KINDS = ['vpc', 'igw', 'subnet', 'eni', 'dopt', 'eipalloc', 'sg', 'rtb']

View File

@ -51,6 +51,10 @@ CONF.register_opts(ec2_opts)
"""Instance related API implementation """Instance related API implementation
""" """
Validator = common.Validator
# TODO(ft): implement DeviceIndex # TODO(ft): implement DeviceIndex

View File

@ -27,6 +27,9 @@ from ec2api import exception
from ec2api.openstack.common.gettextutils import _ from ec2api.openstack.common.gettextutils import _
Validator = common.Validator
def create_route_table(context, vpc_id): def create_route_table(context, vpc_id):
vpc = ec2utils.get_db_item(context, 'vpc', vpc_id) vpc = ec2utils.get_db_item(context, 'vpc', vpc_id)
route_table = _create_route_table(context, vpc) route_table = _create_route_table(context, vpc)

View File

@ -37,6 +37,9 @@ LOG = logging.getLogger(__name__)
""" """
Validator = common.Validator
def create_subnet(context, vpc_id, cidr_block, def create_subnet(context, vpc_id, cidr_block,
availability_zone=None): availability_zone=None):
vpc = ec2utils.get_db_item(context, 'vpc', vpc_id) vpc = ec2utils.get_db_item(context, 'vpc', vpc_id)

View File

@ -44,17 +44,13 @@ def validate_dummy(val, **kwargs):
return True return True
def validate_str(max_length=None): def validate_str(val, parameter_name, max_length=None):
if (isinstance(val, basestring) and
def _do(val, parameter_name, **kwargs): (max_length is None or max_length and len(val) <= max_length)):
if (isinstance(val, basestring) and return True
(max_length is None or max_length and len(val) <= max_length)): raise exception.ValidationError(
return True reason=_("%s should not be greater "
raise exception.ValidationError( "than 255 characters.") % parameter_name)
reason=_("%s should not be greater "
"than 255 characters.") % parameter_name)
return _do
def validate_int(max_value=None): def validate_int(max_value=None):
@ -98,6 +94,14 @@ def validate_image_path(val, parameter_name=None, **kwargs):
return True return True
def validate_list(items, parameter_name):
if not isinstance(items, list):
raise exception.InvalidParameterValue(
value=items,
parameter=parameter_name,
reason='Expected a list here')
def validate_user_data(user_data, **kwargs): def validate_user_data(user_data, **kwargs):
"""Check if the user_data is encoded properly.""" """Check if the user_data is encoded properly."""
try: try:
@ -164,38 +168,37 @@ def validate_cidr(cidr, parameter_name, **kwargs):
return True return True
def validate_cidr_block(cidr, action, **kwargs): def _validate_cidr_block(cidr):
validate_cidr(cidr, 'cidrBlock') validate_cidr(cidr, 'cidrBlock')
size = int(cidr.split("/")[-1]) size = int(cidr.split("/")[-1])
if size > 28 or size < 16: return size >= 16 and size <= 28
if action == 'CreateVpc':
raise exception.InvalidVpcRange(cidr_block=cidr)
elif action == 'CreateSubnet': def validate_vpc_cidr(cidr):
raise exception.InvalidSubnetRange(cidr_block=cidr) if not _validate_cidr_block(cidr):
return True raise exception.InvalidVpcRange(cidr_block=cidr)
def validate_subnet_cidr(cidr):
if not _validate_cidr_block(cidr):
raise exception.InvalidSubnetRange(cidr_block=cidr)
# NOTE(Alex) Unfortunately Amazon returns various kinds of error for invalid # NOTE(Alex) Unfortunately Amazon returns various kinds of error for invalid
# IDs (...ID.Malformed, ...Id.Malformed, ...ID.NotFound, InvalidParameterValue) # IDs (...ID.Malformed, ...Id.Malformed, ...ID.NotFound, InvalidParameterValue)
# So we decided here to commonize invalid IDs to InvalidParameterValue error. # So we decided here to commonize invalid IDs to InvalidParameterValue error.
def validate_ec2_id(prefices): def validate_ec2_id(val, parameter_name, prefices):
try:
def _do(val, parameter_name, **kwargs): prefix, value = val.rsplit('-', 1)
if not validate_str()(val, parameter_name, **kwargs): int(value, 16)
return False if prefix in prefices:
try: return True
prefix, value = val.rsplit('-', 1) except Exception:
int(value, 16) pass
if prefix in prefices: raise exception.InvalidParameterValue(
return True value=val, parameter=parameter_name,
except Exception: reason=_('Expected: %(prefix)s-...') % {'prefix': prefices[0]})
pass
raise exception.InvalidParameterValue(
value=val, parameter=parameter_name,
reason=_('Expected: %(prefix)s-...') % {'prefix': prefices[0]})
return _do
def validate_ec2_association_id(id, parameter_name, action): def validate_ec2_association_id(id, parameter_name, action):

View File

@ -38,6 +38,9 @@ LOG = logging.getLogger(__name__)
""" """
Validator = common.Validator
def create_vpc(context, cidr_block, instance_tenancy='default'): def create_vpc(context, cidr_block, instance_tenancy='default'):
neutron = clients.neutron(context) neutron = clients.neutron(context)
# TODO(Alex): Handle errors like overlimit # TODO(Alex): Handle errors like overlimit

View File

@ -87,7 +87,7 @@ class DbApiTestCase(test_base.BaseTestCase):
self.assertIn('id', item) self.assertIn('id', item)
self.assertIsNotNone(item['id']) self.assertIsNotNone(item['id'])
item_id = item.pop('id') item_id = item.pop('id')
self.assertTrue(validator.validate_ec2_id(('fake',))(item_id, '')) self.assertTrue(validator.validate_ec2_id(item_id, '', ['fake']))
self.assertThat(item, matchers.DictMatches(new_item, self.assertThat(item, matchers.DictMatches(new_item,
orderless_lists=True)) orderless_lists=True))
@ -146,7 +146,7 @@ class DbApiTestCase(test_base.BaseTestCase):
def test_add_item_id(self): def test_add_item_id(self):
os_id = fakes.random_os_id() os_id = fakes.random_os_id()
item_id = db_api.add_item_id(self.context, 'fake', os_id) item_id = db_api.add_item_id(self.context, 'fake', os_id)
self.assertTrue(validator.validate_ec2_id(('fake',))(item_id, '')) self.assertTrue(validator.validate_ec2_id(item_id, '', ['fake']))
item = db_api.get_item_by_id(self.context, 'fake', item_id) item = db_api.get_item_by_id(self.context, 'fake', item_id)
self.assertIsNone(item) self.assertIsNone(item)
item = db_api.add_item(self.context, 'fake', {'os_id': os_id}) item = db_api.add_item(self.context, 'fake', {'os_id': os_id})

View File

@ -46,17 +46,11 @@ class EC2ValidationTestCase(testtools.TestCase):
check_raise_invalid_parameter('10.10.0.0/33') check_raise_invalid_parameter('10.10.0.0/33')
check_raise_invalid_parameter('10.10.0.0/-1') check_raise_invalid_parameter('10.10.0.0/-1')
def check_raise_invalid_vpc_range(cidr, ex_class, action): self.assertRaises(exception.InvalidSubnetRange,
self.assertRaises(ex_class, validator.validate_subnet_cidr, '10.10.0.0/15')
validator.validate_cidr_block, cidr,
action)
check_raise_invalid_vpc_range('10.10.0.0/15', self.assertRaises(exception.InvalidVpcRange,
exception.InvalidSubnetRange, validator.validate_vpc_cidr, '10.10.0.0/29')
'CreateSubnet')
check_raise_invalid_vpc_range('10.10.0.0/29',
exception.InvalidVpcRange,
'CreateVpc')
class EC2TimestampValidationTestCase(testtools.TestCase): class EC2TimestampValidationTestCase(testtools.TestCase):

View File

@ -508,6 +508,16 @@ class InstanceTestCase(base.ApiTestCase):
fakes.EC2_RESERVATION_2]}, fakes.EC2_RESERVATION_2]},
orderless_lists=True)) orderless_lists=True))
self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_2]
resp = self.execute('DescribeInstances', {'InstanceId.1':
fakes.ID_EC2_INSTANCE_2})
self.assertEqual(200, resp['status'])
resp.pop('status')
self.assertThat(resp, matchers.DictMatches(
{'reservationSet': [fakes.EC2_RESERVATION_2]},
orderless_lists=True))
# TODO(ft): restore test after finish extraction of Nova EC2 API # TODO(ft): restore test after finish extraction of Nova EC2 API
def _test_describe_instances_mutliple_networks(self): def _test_describe_instances_mutliple_networks(self):
"""Describe 2 instances with various combinations of network.""" """Describe 2 instances with various combinations of network."""

View File

@ -189,7 +189,7 @@ class RouteTableTestCase(base.ApiTestCase):
do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2, do_check({'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
'DestinationCidrBlock': '0.0.0.0/0', 'DestinationCidrBlock': '0.0.0.0/0',
'NetworkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1, 'NetworkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1,
'GatewayId': fakes.ID_EC2_NETWORK_INTERFACE_1}, 'GatewayId': fakes.ID_EC2_IGW_1},
'InvalidParameterCombination') 'InvalidParameterCombination')
# NOTE(ft): gateway from different vpc # NOTE(ft): gateway from different vpc