Implement customer gateway

Change-Id: I0e090f1d8dca70235615242f8993081c71ae6615
This commit is contained in:
Feodor Tersin 2015-05-16 12:34:57 +03:00
parent 76b5766d0b
commit 54690d1095
10 changed files with 328 additions and 1 deletions

View File

@ -27,6 +27,7 @@ from oslo_log import log as logging
from ec2api.api import address
from ec2api.api import availability_zone
from ec2api.api import customer_gateway
from ec2api.api import dhcp_options
from ec2api.api import image
from ec2api.api import instance
@ -1775,3 +1776,58 @@ class VpcCloudController(CloudController):
Returns:
true if the request succeeds.
"""
@module_and_param_types(customer_gateway, 'ip', 'vpn_connection_type',
'int')
def create_customer_gateway(self, context, ip_address, type,
bgp_asn=None):
"""Provides information to EC2 API about VPN customer gateway device.
Args:
context (RequestContext): The request context.
ip_address (str): The Internet-routable IP address for the
customer gateway's outside interface.
type (str): The type of VPN connection that this customer gateway
supports (ipsec.1).
bgp_asn (int): For devices that support BGP,
the customer gateway's BGP ASN (65000 otherwise).
Returns:
Information about the customer gateway.
You cannot create more than one customer gateway with the same VPN
type, IP address, and BGP ASN parameter values. If you run an
identical request more than one time, subsequent requests return
information about the existing customer gateway.
"""
@module_and_param_types(customer_gateway, 'cgw_id')
def delete_customer_gateway(self, context, customer_gateway_id):
"""Deletes the specified customer gateway.
Args:
context (RequestContext): The request context.
customer_gateway_id (str): The ID of the customer gateway.
Returns:
true if the request succeeds.
You must delete the VPN connection before you can delete the customer
gateway.
"""
@module_and_param_types(customer_gateway, 'cgw_ids',
'filter')
def describe_customer_gateways(self, context, customer_gateway_id=None,
filter=None):
"""Describes one or more of your VPN customer gateways.
Args:
context (RequestContext): The request context.
customer_gateway_id (list of str): One or more customer gateway
IDs.
filter (list of filter dict): One or more filters.
Returns:
Information about one or more customer gateways.
"""

View File

@ -247,6 +247,12 @@ class Validator(object):
def dopt_ids(self, ids):
self.multi(ids, self.dopt_id)
def cgw_id(self, id):
self.ec2_id(id, ['cgw'])
def cgw_ids(self, ids):
self.multi(ids, self.cgw_id)
def security_group_str(self, value):
validator.validate_security_group_str(value, self.param_name,
self.params.get('vpc_id'))
@ -254,8 +260,12 @@ class Validator(object):
def security_group_strs(self, values):
self.multi(values, self.security_group_str)
def vpn_connection_type(self, value):
validator.validate_vpn_connection_type(value)
VPC_KINDS = ['vpc', 'igw', 'subnet', 'eni', 'dopt', 'eipalloc', 'sg', 'rtb']
VPC_KINDS = ['vpc', 'igw', 'subnet', 'eni', 'dopt', 'eipalloc', 'sg', 'rtb',
'cgw']
class UniversalDescriber(object):

View File

@ -0,0 +1,76 @@
# 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.
from ec2api.api import common
from ec2api.api import ec2utils
from ec2api.db import api as db_api
from ec2api import exception
from ec2api.i18n import _
Validator = common.Validator
DEFAULT_BGP_ASN = 65000
def create_customer_gateway(context, ip_address, type, bgp_asn=None):
if bgp_asn and bgp_asn != DEFAULT_BGP_ASN:
raise exception.Unsupported("BGP dynamic routing is unsupported")
customer_gateway = next((cgw for cgw in db_api.get_items(context, 'cgw')
if cgw['ip_address'] == ip_address), None)
if not customer_gateway:
customer_gateway = db_api.add_item(context, 'cgw',
{'ip_address': ip_address})
return {'customerGateway': _format_customer_gateway(customer_gateway)}
def delete_customer_gateway(context, customer_gateway_id):
customer_gateway = ec2utils.get_db_item(context, customer_gateway_id)
vpn_connections = db_api.get_items(context, 'vpn')
if any(vpn['customer_gateway_id'] == customer_gateway['id']
for vpn in vpn_connections):
raise exception.IncorrectState(
reason=_('The customer gateway is in use.'))
db_api.delete_item(context, customer_gateway['id'])
return True
def describe_customer_gateways(context, customer_gateway_id=None,
filter=None):
formatted_cgws = CustomerGatewayDescriber().describe(
context, ids=customer_gateway_id, filter=filter)
return {'customerGatewaySet': formatted_cgws}
class CustomerGatewayDescriber(common.TaggableItemsDescriber,
common.NonOpenstackItemsDescriber):
KIND = 'cgw'
FILTER_MAP = {'bgp-asn': 'bgpAsn',
'customer-gateway-id': 'customerGatewayId',
'ip-address': 'ipAddress',
'state': 'state',
'type': 'type'}
def format(self, customer_gateway):
return _format_customer_gateway(customer_gateway)
def _format_customer_gateway(customer_gateway):
return {'customerGatewayId': customer_gateway['id'],
'ipAddress': customer_gateway['ip_address'],
'state': 'available',
'type': 'ipsec.1',
'bgpAsn': DEFAULT_BGP_ASN}

View File

@ -189,6 +189,7 @@ NOT_FOUND_EXCEPTION_MAP = {
'ami': exception.InvalidAMIIDNotFound,
'aki': exception.InvalidAMIIDNotFound,
'ari': exception.InvalidAMIIDNotFound,
'cgw': exception.InvalidCustomerGatewayIDNotFound,
}

View File

@ -224,3 +224,11 @@ def validate_security_group_str(value, parameter_name, vpc_id=None):
if msg:
raise exception.ValidationError(reason=msg)
return True
def validate_vpn_connection_type(value):
if value != 'ipsec.1':
raise exception.InvalidParameterValue(
value=type, parameter='type',
reason=_('Invalid VPN connection type.'))
return True

View File

@ -400,6 +400,11 @@ class InvalidAvailabilityZoneNotFound(EC2NotFoundException):
msg_fmt = _("Availability zone %(id)s not found")
class InvalidCustomerGatewayIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidCustomerGatewayID.NotFound'
msg_fmt = _("The customerGateway ID '%(id)s' does not exist")
class ResourceLimitExceeded(EC2OverlimitException):
msg_fmt = _('You have reached the limit of %(resource)s')

View File

@ -234,6 +234,14 @@ FINGERPRINT_KEY_PAIR = (
'2a:72:dd:aa:0d:a6:45:4d:27:4f:75:28:73:0d:a6:10:35:88:e1:ce')
# customer gateway constants
ID_EC2_CUSTOMER_GATEWAY_1 = random_ec2_id('cgw')
ID_EC2_CUSTOMER_GATEWAY_2 = random_ec2_id('cgw')
IP_CUSTOMER_GATEWAY_ADDRESS_1 = '172.16.1.11'
IP_CUSTOMER_GATEWAY_ADDRESS_2 = '172.31.2.22'
# Object constants section
# Constant name notation:
# [<subtype>]<object_name>
@ -1489,6 +1497,36 @@ EC2_KEY_PAIR = {'keyName': NAME_KEY_PAIR,
'keyMaterial': PRIVATE_KEY_KEY_PAIR}
# customer gateway objects
DB_CUSTOMER_GATEWAY_1 = {
'id': ID_EC2_CUSTOMER_GATEWAY_1,
'ip_address': IP_CUSTOMER_GATEWAY_ADDRESS_1,
'os_id': None,
'vpc_id': None,
}
DB_CUSTOMER_GATEWAY_2 = {
'id': ID_EC2_CUSTOMER_GATEWAY_2,
'ip_address': IP_CUSTOMER_GATEWAY_ADDRESS_2,
'os_id': None,
'vpc_id': None,
}
EC2_CUSTOMER_GATEWAY_1 = {
'customerGatewayId': ID_EC2_CUSTOMER_GATEWAY_1,
'ipAddress': IP_CUSTOMER_GATEWAY_ADDRESS_1,
'state': 'available',
'type': 'ipsec.1',
'bgpAsn': 65000,
}
EC2_CUSTOMER_GATEWAY_2 = {
'customerGatewayId': ID_EC2_CUSTOMER_GATEWAY_2,
'ipAddress': IP_CUSTOMER_GATEWAY_ADDRESS_2,
'state': 'available',
'type': 'ipsec.1',
'bgpAsn': 65000,
}
# Object generator functions section
# internet gateway generator functions

View File

@ -0,0 +1,119 @@
# 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 mock
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 CustomerGatewayTestCase(base.ApiTestCase):
def test_create_customer_gateway(self):
self.db_api.add_item.side_effect = (
tools.get_db_api_add_item(fakes.ID_EC2_CUSTOMER_GATEWAY_2))
resp = self.execute('CreateCustomerGateway',
{'IpAddress': fakes.IP_CUSTOMER_GATEWAY_ADDRESS_2,
'Type': 'ipsec.1'})
self.assertEqual({'customerGateway': fakes.EC2_CUSTOMER_GATEWAY_2},
resp)
self.db_api.add_item.assert_called_once_with(
mock.ANY, 'cgw',
{'ip_address': fakes.IP_CUSTOMER_GATEWAY_ADDRESS_2},
project_id=None)
resp = self.execute('CreateCustomerGateway',
{'IpAddress': fakes.IP_CUSTOMER_GATEWAY_ADDRESS_2,
'Type': 'ipsec.1',
'BgpAsn': '65000'})
self.assertEqual({'customerGateway': fakes.EC2_CUSTOMER_GATEWAY_2},
resp)
def test_create_customer_gateway_idempotent(self):
self.set_mock_db_items(fakes.DB_CUSTOMER_GATEWAY_1)
resp = self.execute('CreateCustomerGateway',
{'IpAddress': fakes.IP_CUSTOMER_GATEWAY_ADDRESS_1,
'Type': 'ipsec.1'})
self.assertEqual({'customerGateway': fakes.EC2_CUSTOMER_GATEWAY_1},
resp)
self.assertFalse(self.db_api.add_item.called)
resp = self.execute('CreateCustomerGateway',
{'IpAddress': fakes.IP_CUSTOMER_GATEWAY_ADDRESS_1,
'Type': 'ipsec.1',
'BgpAsn': '65000'})
self.assertEqual({'customerGateway': fakes.EC2_CUSTOMER_GATEWAY_1},
resp)
self.assertFalse(self.db_api.add_item.called)
def test_create_customer_gateway_invalid_parameters(self):
self.assert_execution_error(
'Unsupported',
'CreateCustomerGateway',
{'IpAddress': fakes.IP_CUSTOMER_GATEWAY_ADDRESS_1,
'Type': 'ipsec.1',
'BgpAsn': '456'})
def test_delete_customer_gateway(self):
self.set_mock_db_items(fakes.DB_CUSTOMER_GATEWAY_2)
resp = self.execute(
'DeleteCustomerGateway',
{'CustomerGatewayId': fakes.ID_EC2_CUSTOMER_GATEWAY_2})
self.assertEqual({'return': True}, resp)
self.db_api.delete_item.assert_called_once_with(
mock.ANY, fakes.ID_EC2_CUSTOMER_GATEWAY_2)
def test_delete_customer_gateway_invalid_parameters(self):
self.set_mock_db_items()
self.assert_execution_error(
'InvalidCustomerGatewayID.NotFound',
'DeleteCustomerGateway',
{'CustomerGatewayId': fakes.ID_EC2_CUSTOMER_GATEWAY_2})
self.assertFalse(self.db_api.delete_item.called)
def test_describe_customer_gateways(self):
self.set_mock_db_items(fakes.DB_CUSTOMER_GATEWAY_1,
fakes.DB_CUSTOMER_GATEWAY_2)
resp = self.execute('DescribeCustomerGateways', {})
self.assertThat(resp['customerGatewaySet'],
matchers.ListMatches([fakes.EC2_CUSTOMER_GATEWAY_1,
fakes.EC2_CUSTOMER_GATEWAY_2]))
resp = self.execute(
'DescribeCustomerGateways',
{'CustomerGatewayId.1': fakes.ID_EC2_CUSTOMER_GATEWAY_2})
self.assertThat(
resp['customerGatewaySet'],
matchers.ListMatches([fakes.EC2_CUSTOMER_GATEWAY_2]))
self.db_api.get_items_by_ids.assert_called_once_with(
mock.ANY, set([fakes.ID_EC2_CUSTOMER_GATEWAY_2]))
self.check_filtering(
'DescribeCustomerGateways', 'customerGatewaySet',
[('bgp-asn', 65000),
('customer-gateway-id', fakes.ID_EC2_CUSTOMER_GATEWAY_2),
('ip-address', fakes.IP_CUSTOMER_GATEWAY_ADDRESS_2),
('state', 'available'),
('type', 'ipsec.1')])
self.check_tag_support(
'DescribeCustomerGateways', 'customerGatewaySet',
fakes.ID_EC2_CUSTOMER_GATEWAY_2, 'customerGatewayId')

View File

@ -76,6 +76,7 @@ class EC2ValidationTestCase(testtools.TestCase):
validator.eipalloc_id('eipalloc-00000001')
validator.eipassoc_id('eipassoc-00000001')
validator.rtbassoc_id('rtbassoc-00000001')
validator.cgw_id('cgw-00000001')
invalid_ids = ['1234', 'a-1111', '', 'i-1111', 'i-rrr', 'foobar']
@ -97,6 +98,7 @@ class EC2ValidationTestCase(testtools.TestCase):
check_raise_invalid_parameters(validator.eipalloc_id)
check_raise_invalid_parameters(validator.eipassoc_id)
check_raise_invalid_parameters(validator.rtbassoc_id)
check_raise_invalid_parameters(validator.cgw_id)
invalid_ids = ['1234', 'a-1111', '', 'vpc-1111', 'vpc-rrr', 'foobar']
@ -158,6 +160,16 @@ class EC2ValidationTestCase(testtools.TestCase):
check_raise_validation_error('aa #^% -=99')
check_raise_validation_error('x' * 256)
def test_validate_vpn_connection_type(self):
validator = common.Validator()
validator.vpn_connection_type('ipsec.1')
invalid_ids = ['1234', 'a-1111', '', 'vpc-1111', 'vpc-rrr', 'foobar',
'ipsec1', 'openvpn', 'pptp', 'l2tp', 'freelan']
for id in invalid_ids:
self.assertRaises(exception.InvalidParameterValue,
validator.vpn_connection_type, id)
class EC2TimestampValidationTestCase(testtools.TestCase):
"""Test case for EC2 request timestamp validation."""

View File

@ -67,6 +67,7 @@ class EC2UtilsTestCase(testtools.TestCase):
check_not_found('ami', exception.InvalidAMIIDNotFound)
check_not_found('ari', exception.InvalidAMIIDNotFound)
check_not_found('aki', exception.InvalidAMIIDNotFound)
check_not_found('cgw', exception.InvalidCustomerGatewayIDNotFound)
@mock.patch('ec2api.db.api.IMPL')
def test_get_db_items(self, db_api):
@ -121,6 +122,7 @@ class EC2UtilsTestCase(testtools.TestCase):
check_not_found('ami', exception.InvalidAMIIDNotFound)
check_not_found('aki', exception.InvalidAMIIDNotFound)
check_not_found('ari', exception.InvalidAMIIDNotFound)
check_not_found('cgw', exception.InvalidCustomerGatewayIDNotFound)
"""Unit test api xml conversion."""
def test_number_conversion(self):