Merge "Implementation of Brocade Vyatta VPNaaS Plugin"
This commit is contained in:
commit
dfad4a1969
@ -8,4 +8,6 @@
|
||||
service_provider=VPN:openswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default
|
||||
# Uncomment the following line (and comment out the OpenSwan VPN line) to enable Cisco's VPN driver.
|
||||
# service_provider=VPN:cisco:neutron_vpnaas.services.vpn.service_drivers.cisco_ipsec.CiscoCsrIPsecVPNDriver:default
|
||||
# Uncomment the following line (and comment out the OpenSwan VPN line) to enable Brocade Vyatta's VPN driver.
|
||||
# service_provider=VPN:vyatta:neutron_vpnaas.services.vpn.service_drivers.vyatta_ipsec.VyattaIPsecDriver:default
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
# If we want to use multiple drivers, we need to define this option multiple times.
|
||||
# vpn_device_driver=neutron.services.vpn.device_drivers.ipsec.OpenSwanDriver
|
||||
# vpn_device_driver=neutron.services.vpn.device_drivers.cisco_ipsec.CiscoCsrIPsecDriver
|
||||
# vpn_device_driver=neutron.services.vpn.device_drivers.vyatta_ipsec.VyattaIPSecDriver
|
||||
# vpn_device_driver=another_driver
|
||||
|
||||
[ipsec]
|
||||
|
@ -18,3 +18,5 @@ IPSEC_DRIVER_TOPIC = 'ipsec_driver'
|
||||
IPSEC_AGENT_TOPIC = 'ipsec_agent'
|
||||
CISCO_IPSEC_DRIVER_TOPIC = 'cisco_csr_ipsec_driver'
|
||||
CISCO_IPSEC_AGENT_TOPIC = 'cisco_csr_ipsec_agent'
|
||||
BROCADE_IPSEC_DRIVER_TOPIC = 'brocade_vyatta_ipsec_driver'
|
||||
BROCADE_IPSEC_AGENT_TOPIC = 'brocade_vyatta_ipsec_agent'
|
||||
|
308
neutron_vpnaas/services/vpn/device_drivers/vyatta_ipsec.py
Normal file
308
neutron_vpnaas/services/vpn/device_drivers/vyatta_ipsec.py
Normal file
@ -0,0 +1,308 @@
|
||||
# Copyright 2015 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 collections
|
||||
import logging
|
||||
|
||||
import oslo_messaging as messaging
|
||||
import pprint
|
||||
import six
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron import context as n_ctx
|
||||
from neutron.i18n import _LE, _LW
|
||||
from neutron.openstack.common import loopingcall
|
||||
from neutron.openstack.common import periodic_task
|
||||
from vyatta.common import exceptions as v_exc
|
||||
from vyatta.common import vrouter_config
|
||||
from vyatta.vpn import config as vyatta_vpn_config
|
||||
|
||||
from neutron_vpnaas.services.vpn.common import topics
|
||||
from neutron_vpnaas.services.vpn import device_drivers
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_KEY_CONNECTIONS = 'ipsec_site_connections'
|
||||
_KEY_IKEPOLICY = 'ikepolicy'
|
||||
_KEY_ESPPOLICY = 'ipsecpolicy'
|
||||
|
||||
|
||||
class _DriverRPCEndpoint(object):
|
||||
"""
|
||||
VPN device driver RPC endpoint (server > agent)
|
||||
|
||||
history
|
||||
1.0 Initial version
|
||||
"""
|
||||
|
||||
target = messaging.Target(version='1.0')
|
||||
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def vpnservice_updated(self, context, **kwargs):
|
||||
self.driver.sync(context, [])
|
||||
|
||||
|
||||
class NeutronServerAPI(object):
|
||||
"""
|
||||
VPN service driver RPC endpoint (agent > server)
|
||||
"""
|
||||
|
||||
def __init__(self, topic):
|
||||
target = messaging.Target(topic=topic, version='1.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def get_vpn_services_on_host(self, context, host):
|
||||
# make RPC call to neutron server
|
||||
cctxt = self.client.prepare()
|
||||
data = cctxt.call(context, 'get_vpn_services_on_host', host=host)
|
||||
|
||||
vpn_services = list()
|
||||
for svc in data:
|
||||
try:
|
||||
for conn in svc[_KEY_CONNECTIONS]:
|
||||
vyatta_vpn_config.validate_svc_connection(conn)
|
||||
except v_exc.InvalidVPNServiceError:
|
||||
LOG.error(_LE('Invalid or incomplete VPN service data: '
|
||||
'id={id}').format(id=svc.get('id')))
|
||||
continue
|
||||
vpn_services.append(svc)
|
||||
|
||||
# return transformed data to caller
|
||||
return vpn_services
|
||||
|
||||
def update_status(self, context, status):
|
||||
cctxt = self.client.prepare()
|
||||
cctxt.cast(context, 'update_status', status=status)
|
||||
|
||||
|
||||
class VyattaIPSecDriver(device_drivers.DeviceDriver):
|
||||
"""
|
||||
Vyatta VPN device driver
|
||||
"""
|
||||
rpc_endpoint_factory = _DriverRPCEndpoint
|
||||
|
||||
def __init__(self, vpn_service, host):
|
||||
super(VyattaIPSecDriver, self).__init__(vpn_service, host)
|
||||
self.vpn_service = vpn_service
|
||||
self.host = host
|
||||
|
||||
# register RPC endpoint
|
||||
conn = n_rpc.create_connection(new=True)
|
||||
node_topic = '%s.%s' % (topics.BROCADE_IPSEC_AGENT_TOPIC,
|
||||
self.host)
|
||||
|
||||
endpoints = [self.rpc_endpoint_factory(self)]
|
||||
conn.create_consumer(node_topic, endpoints, fanout=False)
|
||||
conn.consume_in_threads()
|
||||
|
||||
# initialize agent ot server RPC link
|
||||
self.server_api = NeutronServerAPI(
|
||||
topics.BROCADE_IPSEC_DRIVER_TOPIC)
|
||||
|
||||
# initialize VPN service cache (to keep service state)
|
||||
self._svc_cache = list()
|
||||
self._router_resources_cache = dict()
|
||||
|
||||
# setup periodic task. All periodic task require fully configured
|
||||
# device driver. It will be called asynchronously, and soon, so it
|
||||
# should be last, when all configuration is done.
|
||||
self._periodic_tasks = periodic = _VyattaPeriodicTasks(self)
|
||||
loop = loopingcall.DynamicLoopingCall(periodic)
|
||||
loop.start(initial_delay=5)
|
||||
|
||||
def sync(self, context, processes):
|
||||
"""
|
||||
Called by _DriverRPCEndpoint instance.
|
||||
"""
|
||||
svc_update = self.server_api.get_vpn_services_on_host(
|
||||
context, self.host)
|
||||
to_del, to_change, to_add = self._svc_diff(
|
||||
self._svc_cache, svc_update)
|
||||
|
||||
for svc in to_del:
|
||||
resources = self.get_router_resources(svc['router_id'])
|
||||
self._svc_delete(svc, resources)
|
||||
|
||||
for old, new in to_change:
|
||||
resources = self.get_router_resources(old['router_id'])
|
||||
self._svc_delete(old, resources)
|
||||
self._svc_add(new, resources)
|
||||
|
||||
for svc in to_add:
|
||||
resources = self.get_router_resources(svc['router_id'])
|
||||
self._svc_add(svc, resources)
|
||||
|
||||
self._svc_cache = svc_update
|
||||
|
||||
def create_router(self, router_id):
|
||||
vrouter = self.vpn_service.get_router_client(router_id)
|
||||
config_raw = vrouter.get_vrouter_configuration()
|
||||
|
||||
resources = self.get_router_resources(router_id)
|
||||
with resources.make_patch() as patch:
|
||||
vrouter_svc = vyatta_vpn_config.parse_vrouter_config(
|
||||
vrouter_config.parse_config(config_raw), patch)
|
||||
for svc in vrouter_svc:
|
||||
svc['router_id'] = router_id
|
||||
|
||||
self._svc_cache.extend(vrouter_svc)
|
||||
|
||||
def destroy_router(self, router_id):
|
||||
to_del = list()
|
||||
for idx, svc in enumerate(self._svc_cache):
|
||||
if svc['router_id'] != router_id:
|
||||
continue
|
||||
resources = self.get_router_resources(svc['router_id'])
|
||||
self._svc_delete(svc, resources)
|
||||
to_del.insert(0, idx)
|
||||
|
||||
for idx in to_del:
|
||||
del self._svc_cache[idx]
|
||||
|
||||
def _svc_add(self, svc, resources):
|
||||
vrouter = self.vpn_service.get_router_client(svc['router_id'])
|
||||
|
||||
for conn in svc[_KEY_CONNECTIONS]:
|
||||
with resources.make_patch() as patch:
|
||||
iface = self._get_router_gw_iface(vrouter, svc['router_id'])
|
||||
batch = vyatta_vpn_config.connect_setup_commands(
|
||||
vrouter, iface, svc, conn, patch)
|
||||
vrouter.exec_cmd_batch(batch)
|
||||
|
||||
def _svc_delete(self, svc, resources):
|
||||
vrouter = self.vpn_service.get_router_client(svc['router_id'])
|
||||
|
||||
for conn in svc[_KEY_CONNECTIONS]:
|
||||
with resources.make_patch() as patch:
|
||||
iface = self._get_router_gw_iface(vrouter, svc['router_id'])
|
||||
batch = vyatta_vpn_config.connect_remove_commands(
|
||||
vrouter, iface, svc, conn, patch)
|
||||
vrouter.exec_cmd_batch(batch)
|
||||
|
||||
def _svc_diff(self, svc_old, svc_new):
|
||||
state_key = 'admin_state_up'
|
||||
|
||||
old_idnr = set(x['id'] for x in svc_old)
|
||||
new_idnr = set(x['id'] for x in svc_new if x[state_key])
|
||||
to_del = old_idnr - new_idnr
|
||||
to_add = new_idnr - old_idnr
|
||||
possible_change = old_idnr & new_idnr
|
||||
|
||||
svc_old = dict((x['id'], x) for x in svc_old)
|
||||
svc_new = dict((x['id'], x) for x in svc_new)
|
||||
|
||||
to_del = [svc_old[x] for x in to_del]
|
||||
to_add = [svc_new[x] for x in to_add]
|
||||
to_change = list()
|
||||
|
||||
for idnr in possible_change:
|
||||
old = svc_old[idnr]
|
||||
new = svc_new[idnr]
|
||||
|
||||
assert old['router_id'] == new['router_id']
|
||||
|
||||
vrouter = self.vpn_service.get_router_client(old['router_id'])
|
||||
gw_iface = self._get_router_gw_iface(vrouter, old['router_id'])
|
||||
|
||||
if vyatta_vpn_config.compare_vpn_services(
|
||||
vrouter, gw_iface, old, new):
|
||||
continue
|
||||
|
||||
to_change.append((old, new))
|
||||
|
||||
return to_del, to_change, to_add
|
||||
|
||||
def get_active_services(self):
|
||||
return tuple(self._svc_cache)
|
||||
|
||||
def get_router_resources(self, router_id):
|
||||
try:
|
||||
res = self._router_resources_cache[router_id]
|
||||
except KeyError:
|
||||
res = vyatta_vpn_config.RouterResources(router_id)
|
||||
self._router_resources_cache[router_id] = res
|
||||
|
||||
return res
|
||||
|
||||
def update_status(self, ctx, stat):
|
||||
LOG.debug('STAT: %s', pprint.pformat(stat))
|
||||
self.server_api.update_status(ctx, stat)
|
||||
|
||||
def _get_router_gw_iface(self, vrouter, router_id):
|
||||
router_info = self.vpn_service.get_router(router_id)
|
||||
try:
|
||||
gw_interface = vrouter.get_ethernet_if_id(
|
||||
router_info['gw_port']['mac_address'])
|
||||
except KeyError:
|
||||
raise v_exc.InvalidL3AgentStateError(description=_(
|
||||
'Router id={0} have no external gateway.').format(
|
||||
router_info['id']))
|
||||
return gw_interface
|
||||
|
||||
|
||||
class _VyattaPeriodicTasks(periodic_task.PeriodicTasks):
|
||||
def __init__(self, driver):
|
||||
super(_VyattaPeriodicTasks, self).__init__()
|
||||
self.driver = driver
|
||||
|
||||
def __call__(self):
|
||||
ctx_admin = n_ctx.get_admin_context()
|
||||
return self.run_periodic_tasks(ctx_admin)
|
||||
|
||||
@periodic_task.periodic_task(spacing=5)
|
||||
def grab_vpn_status(self, ctx):
|
||||
LOG.debug('VPN device driver periodic task: grab_vpn_status.')
|
||||
|
||||
svc_by_vrouter = collections.defaultdict(list)
|
||||
for svc in self.driver.get_active_services():
|
||||
svc_by_vrouter[svc['router_id']].append(svc)
|
||||
|
||||
status = list()
|
||||
|
||||
for router_id, svc_set in six.iteritems(svc_by_vrouter):
|
||||
vrouter = self.driver.vpn_service.get_router_client(router_id)
|
||||
resources = self.driver.get_router_resources(router_id)
|
||||
|
||||
try:
|
||||
ipsec_sa = vrouter.get_vpn_ipsec_sa()
|
||||
except v_exc.VRouterOperationError as e:
|
||||
LOG.warn(_LW('Failed to fetch tunnel stats from router '
|
||||
'{0}: {1}').format(router_id, unicode(e)))
|
||||
continue
|
||||
|
||||
conn_ok = vyatta_vpn_config.parse_vpn_connections(
|
||||
ipsec_sa, resources)
|
||||
|
||||
for svc in svc_set:
|
||||
svc_ok = True
|
||||
conn_stat = dict()
|
||||
for conn in svc[_KEY_CONNECTIONS]:
|
||||
ok = conn['id'] in conn_ok
|
||||
svc_ok = svc_ok and ok
|
||||
conn_stat[conn['id']] = {
|
||||
'status': 'ACTIVE' if ok else 'DOWN',
|
||||
'updated_pending_status': True
|
||||
}
|
||||
|
||||
status.append({
|
||||
'id': svc['id'],
|
||||
'status': 'ACTIVE' if svc_ok else 'DOWN',
|
||||
'updated_pending_status': True,
|
||||
'ipsec_site_connections': conn_stat
|
||||
})
|
||||
|
||||
self.driver.update_status(ctx, status)
|
117
neutron_vpnaas/services/vpn/service_drivers/vyatta_ipsec.py
Normal file
117
neutron_vpnaas/services/vpn/service_drivers/vyatta_ipsec.py
Normal file
@ -0,0 +1,117 @@
|
||||
# Copyright 2015 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 netaddr
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
|
||||
from neutron_vpnaas.services.vpn.common import topics
|
||||
from neutron_vpnaas.services.vpn import service_drivers
|
||||
from neutron_vpnaas.services.vpn.service_drivers import ipsec
|
||||
|
||||
IPSEC = 'ipsec'
|
||||
BASE_IPSEC_VERSION = '1.0'
|
||||
|
||||
|
||||
class VyattaIPsecDriver(service_drivers.VpnDriver):
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
super(VyattaIPsecDriver, self).__init__(service_plugin)
|
||||
|
||||
self.endpoints = [ipsec.IPsecVpnDriverCallBack(self)]
|
||||
self.conn = n_rpc.create_connection(new=True)
|
||||
self.conn.create_consumer(
|
||||
topics.BROCADE_IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False)
|
||||
self.conn.consume_in_threads()
|
||||
self.agent_rpc = ipsec.IPsecVpnAgentApi(
|
||||
topics.BROCADE_IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self)
|
||||
|
||||
@property
|
||||
def service_type(self):
|
||||
return IPSEC
|
||||
|
||||
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def update_ipsec_site_connection(
|
||||
self, context, old_ipsec_site_connection, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def create_ikepolicy(self, context, ikepolicy):
|
||||
pass
|
||||
|
||||
def delete_ikepolicy(self, context, ikepolicy):
|
||||
pass
|
||||
|
||||
def update_ikepolicy(self, context, old_ikepolicy, ikepolicy):
|
||||
pass
|
||||
|
||||
def create_ipsecpolicy(self, context, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def delete_ipsecpolicy(self, context, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def update_ipsecpolicy(self, context, old_ipsec_policy, ipsecpolicy):
|
||||
pass
|
||||
|
||||
def create_vpnservice(self, context, vpnservice):
|
||||
pass
|
||||
|
||||
def update_vpnservice(self, context, old_vpnservice, vpnservice):
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def delete_vpnservice(self, context, vpnservice):
|
||||
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
|
||||
|
||||
def _make_vpnservice_dict(self, vpnservice):
|
||||
"""Convert vpnservice information for vpn agent.
|
||||
|
||||
also converting parameter name for vpn agent driver
|
||||
"""
|
||||
vpnservice_dict = dict(vpnservice)
|
||||
vpnservice_dict['ipsec_site_connections'] = []
|
||||
vpnservice_dict['subnet'] = dict(
|
||||
vpnservice.subnet)
|
||||
vpnservice_dict['external_ip'] = vpnservice.router.gw_port[
|
||||
'fixed_ips'][0]['ip_address']
|
||||
for ipsec_site_connection in vpnservice.ipsec_site_connections:
|
||||
ipsec_site_connection_dict = dict(ipsec_site_connection)
|
||||
try:
|
||||
netaddr.IPAddress(ipsec_site_connection['peer_id'])
|
||||
except netaddr.core.AddrFormatError:
|
||||
ipsec_site_connection['peer_id'] = (
|
||||
'@' + ipsec_site_connection['peer_id'])
|
||||
ipsec_site_connection_dict['ikepolicy'] = dict(
|
||||
ipsec_site_connection.ikepolicy)
|
||||
ipsec_site_connection_dict['ipsecpolicy'] = dict(
|
||||
ipsec_site_connection.ipsecpolicy)
|
||||
vpnservice_dict['ipsec_site_connections'].append(
|
||||
ipsec_site_connection_dict)
|
||||
peer_cidrs = [
|
||||
peer_cidr.cidr
|
||||
for peer_cidr in ipsec_site_connection.peer_cidrs]
|
||||
ipsec_site_connection_dict['peer_cidrs'] = peer_cidrs
|
||||
return vpnservice_dict
|
62
neutron_vpnaas/services/vpn/vyatta_agent.py
Normal file
62
neutron_vpnaas/services/vpn/vyatta_agent.py
Normal file
@ -0,0 +1,62 @@
|
||||
# Copyright 2015 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 neutron.agent import l3_agent as entry
|
||||
from neutron.openstack.common import log as logging
|
||||
from oslo_config import cfg
|
||||
from vyatta.common import l3_agent as vyatta_l3
|
||||
|
||||
from neutron_vpnaas.services.vpn import vyatta_vpn_service
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
vpn_agent_opts = [
|
||||
cfg.MultiStrOpt(
|
||||
'vpn_device_driver',
|
||||
default=['neutron_vpnaas.services.vpn.device_drivers.'
|
||||
'vyatta_ipsec.VyattaIPSecDriver'],
|
||||
help=_("The vpn device drivers Neutron will use")),
|
||||
]
|
||||
cfg.CONF.register_opts(vpn_agent_opts, 'vpnagent')
|
||||
|
||||
|
||||
class VyattaVPNAgent(vyatta_l3.L3AgentMiddleware):
|
||||
def __init__(self, host, conf=None):
|
||||
super(VyattaVPNAgent, self).__init__(host, conf)
|
||||
self.service = vyatta_vpn_service.VyattaVPNService.instance(self)
|
||||
self.event_observers.add(self.service)
|
||||
self.devices = self.service.load_device_drivers(host)
|
||||
|
||||
def _router_added(self, router_id, router):
|
||||
super(VyattaVPNAgent, self)._router_added(router_id, router)
|
||||
for device in self.devices:
|
||||
device.create_router(router_id)
|
||||
|
||||
def _router_removed(self, router_id):
|
||||
for device in self.devices:
|
||||
device.destroy_router(router_id)
|
||||
super(VyattaVPNAgent, self)._router_removed(router_id)
|
||||
|
||||
def _process_router_if_compatible(self, router):
|
||||
super(VyattaVPNAgent, self)._process_router_if_compatible(router)
|
||||
for device in self.devices:
|
||||
device.sync(self.context, None)
|
||||
|
||||
|
||||
def main():
|
||||
entry.main(
|
||||
manager='neutron_vpnaas.services.vpn.vyatta_agent.VyattaVPNAgent')
|
47
neutron_vpnaas/services/vpn/vyatta_vpn_service.py
Normal file
47
neutron_vpnaas/services/vpn/vyatta_vpn_service.py
Normal file
@ -0,0 +1,47 @@
|
||||
# Copyright 2015 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 neutron_vpnaas.services.vpn import vpn_service
|
||||
|
||||
|
||||
class VyattaVPNService(vpn_service.VPNService):
|
||||
"""Vyatta VPN Service handler."""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self, l3_agent):
|
||||
"""Creates a Vyatta VPN Service instance.
|
||||
|
||||
DO NOT CALL THIS DIRECTLY! Use the instance() class method to Create
|
||||
a singleton instance of the service.
|
||||
|
||||
NOTE: Directly accessing l3_agent here is an interim solution
|
||||
until we move to have a router object given down to device drivers
|
||||
to access router related methods
|
||||
"""
|
||||
super(VyattaVPNService, self).__init__(l3_agent)
|
||||
|
||||
def get_router_client(self, router_id):
|
||||
"""
|
||||
Get Router RESTapi client
|
||||
"""
|
||||
return self.l3_agent.get_router_client(router_id)
|
||||
|
||||
def get_router(self, router_id):
|
||||
"""
|
||||
Get Router Object
|
||||
"""
|
||||
return self.l3_agent.get_router(router_id)
|
@ -0,0 +1,222 @@
|
||||
# Copyright 2015 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 contextlib
|
||||
import sys
|
||||
|
||||
import mock
|
||||
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron_vpnaas.tests import base
|
||||
|
||||
with mock.patch.dict(sys.modules, {
|
||||
'vyatta': mock.Mock(),
|
||||
'vyatta.common': mock.Mock(),
|
||||
'vyatta.vrouter': mock.Mock(),
|
||||
'vyatta.vpn': mock.Mock(),
|
||||
}):
|
||||
from neutron_vpnaas.services.vpn.device_drivers import vyatta_ipsec
|
||||
from vyatta.common import vrouter_config
|
||||
from vyatta.vpn import config as vyatta_vpn_config
|
||||
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
FAKE_HOST = 'fake_host'
|
||||
|
||||
|
||||
class TestNeutronServerAPI(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNeutronServerAPI, self).setUp()
|
||||
|
||||
get_client_mock = mock.patch(
|
||||
'neutron.common.rpc.get_client').start()
|
||||
self.client = get_client_mock.return_value
|
||||
|
||||
self.api = vyatta_ipsec.NeutronServerAPI('fake-topic')
|
||||
|
||||
def test_get_vpn_services_on_host(self):
|
||||
fake_context = mock.Mock()
|
||||
|
||||
svc_connections = [
|
||||
self._make_svc_connection(),
|
||||
self._make_svc_connection()
|
||||
]
|
||||
|
||||
vpn_services_on_host = [{
|
||||
vyatta_ipsec._KEY_CONNECTIONS: svc_connections
|
||||
}]
|
||||
|
||||
cctxt = self.client.prepare.return_value
|
||||
cctxt.call.return_value = vpn_services_on_host
|
||||
|
||||
vpn_services = self.api.get_vpn_services_on_host(
|
||||
fake_context, FAKE_HOST)
|
||||
|
||||
cctxt.call.assert_called_with(
|
||||
fake_context, 'get_vpn_services_on_host', host=FAKE_HOST)
|
||||
|
||||
validate_func = vyatta_vpn_config.validate_svc_connection
|
||||
for connection in svc_connections:
|
||||
validate_func.assert_any_call(connection)
|
||||
|
||||
self.assertEqual(len(vpn_services), len(vpn_services_on_host))
|
||||
|
||||
def test_update_status(self):
|
||||
context = mock.Mock()
|
||||
fake_status = 'fake-status'
|
||||
|
||||
cctxt = self.client.prepare.return_value
|
||||
|
||||
self.api.update_status(context, 'fake-status')
|
||||
cctxt.cast.assert_called_once_with(
|
||||
context, 'update_status', status=fake_status)
|
||||
|
||||
@staticmethod
|
||||
def _make_svc_connection():
|
||||
return {
|
||||
vyatta_ipsec._KEY_IKEPOLICY: {
|
||||
'encryption_algorithm': 'aes-256',
|
||||
'lifetime_units': 'seconds',
|
||||
},
|
||||
vyatta_ipsec._KEY_ESPPOLICY: {
|
||||
'encryption_algorithm': 'aes-256',
|
||||
'lifetime_units': 'seconds',
|
||||
'transform_protocol': 'esp',
|
||||
'pfs': 'dh-group2',
|
||||
'encapsulation_mode': 'tunnel'
|
||||
},
|
||||
'dpd_action': 'hold',
|
||||
}
|
||||
|
||||
|
||||
class TestVyattaDeviceDriver(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVyattaDeviceDriver, self).setUp()
|
||||
|
||||
mock.patch('neutron.openstack.common.loopingcall'
|
||||
'.DynamicLoopingCall').start()
|
||||
self.server_api = mock.patch(
|
||||
'neutron_vpnaas.services.vpn.device_drivers'
|
||||
'.vyatta_ipsec.NeutronServerAPI').start()
|
||||
|
||||
self.agent = mock.Mock()
|
||||
|
||||
self.driver = vyatta_ipsec.VyattaIPSecDriver(self.agent, FAKE_HOST)
|
||||
|
||||
def test_create_router(self):
|
||||
router_id = _uuid()
|
||||
|
||||
vrouter_svc_list = [self._make_vrouter_svc()]
|
||||
|
||||
parse_vrouter_config = mock.Mock()
|
||||
parse_vrouter_config.return_value = vrouter_svc_list
|
||||
|
||||
with contextlib.nested(
|
||||
mock.patch.object(vrouter_config, 'parse_config'),
|
||||
mock.patch.object(vyatta_vpn_config, 'parse_vrouter_config',
|
||||
parse_vrouter_config),
|
||||
mock.patch.object(self.driver, 'get_router_resources',
|
||||
mock.MagicMock())
|
||||
):
|
||||
self.driver.create_router(router_id)
|
||||
|
||||
svc_cache = self.driver._svc_cache
|
||||
self.assertEqual(len(svc_cache), 1)
|
||||
self.assertEqual(svc_cache[0]['router_id'], router_id)
|
||||
ipsec_connections = svc_cache[0]['ipsec_site_connections']
|
||||
self.assertEqual(
|
||||
ipsec_connections[0]['peer_address'],
|
||||
'172.24.4.234')
|
||||
|
||||
def test_destroy_router(self):
|
||||
router_id = _uuid()
|
||||
|
||||
get_router_resources = mock.Mock()
|
||||
|
||||
vrouter_svc = self._make_vrouter_svc()
|
||||
vrouter_svc['router_id'] = router_id
|
||||
svc_cache = [vrouter_svc]
|
||||
|
||||
svc_delete = mock.Mock()
|
||||
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, 'get_router_resources',
|
||||
get_router_resources),
|
||||
mock.patch.object(self.driver, '_svc_delete', svc_delete),
|
||||
mock.patch.object(self.driver, '_svc_cache', svc_cache),
|
||||
):
|
||||
self.driver.destroy_router(router_id)
|
||||
|
||||
self.assertNotIn(vrouter_svc, svc_cache)
|
||||
|
||||
svc_delete.assert_called_with(vrouter_svc, mock.ANY)
|
||||
|
||||
def test_sync(self):
|
||||
router_id = _uuid()
|
||||
self.agent.router_info = {
|
||||
router_id: mock.Mock()
|
||||
}
|
||||
|
||||
to_del = [self._make_svc()]
|
||||
to_change = [
|
||||
(self._make_svc(), self._make_svc()),
|
||||
]
|
||||
to_add = [self._make_svc()]
|
||||
|
||||
svc_diff = mock.Mock()
|
||||
svc_diff.return_value = (
|
||||
to_del,
|
||||
to_change,
|
||||
to_add,
|
||||
)
|
||||
|
||||
svc_delete = mock.Mock()
|
||||
svc_add = mock.Mock()
|
||||
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver, '_svc_diff', svc_diff),
|
||||
mock.patch.object(self.driver, '_svc_delete', svc_delete),
|
||||
mock.patch.object(self.driver, '_svc_add', svc_add),
|
||||
):
|
||||
self.driver.sync(mock.Mock(), None)
|
||||
|
||||
for svc in to_add:
|
||||
svc_add.assert_any_call(svc, mock.ANY)
|
||||
|
||||
for svc in to_del:
|
||||
svc_delete.assert_any_call(svc, mock.ANY)
|
||||
|
||||
for old, new in to_change:
|
||||
svc_delete.assert_any_call(old, mock.ANY)
|
||||
svc_add.assert_any_call(new, mock.ANY)
|
||||
|
||||
@staticmethod
|
||||
def _make_vrouter_svc():
|
||||
return {
|
||||
'id': _uuid(),
|
||||
vyatta_ipsec._KEY_CONNECTIONS: [{
|
||||
'peer_address': '172.24.4.234',
|
||||
}]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _make_svc():
|
||||
return {
|
||||
'router_id': _uuid()
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
# Copyright 2015 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 contextlib
|
||||
|
||||
import mock
|
||||
|
||||
from neutron import context as n_ctx
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.plugins.common import constants
|
||||
|
||||
from neutron_vpnaas.services.vpn.service_drivers import vyatta_ipsec
|
||||
from neutron_vpnaas.tests import base
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
FAKE_HOST = 'fake_host'
|
||||
FAKE_SERVICE_ID = _uuid()
|
||||
FAKE_VPN_CONNECTION = {
|
||||
'vpnservice_id': FAKE_SERVICE_ID
|
||||
}
|
||||
FAKE_ROUTER_ID = _uuid()
|
||||
FAKE_VPN_SERVICE = {
|
||||
'router_id': FAKE_ROUTER_ID
|
||||
}
|
||||
|
||||
|
||||
class TestVyattaDriver(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVyattaDriver, self).setUp()
|
||||
mock.patch('neutron.common.rpc.create_connection').start()
|
||||
|
||||
l3_agent = mock.Mock()
|
||||
l3_agent.host = FAKE_HOST
|
||||
plugin = mock.Mock()
|
||||
plugin.get_l3_agents_hosting_routers.return_value = [l3_agent]
|
||||
plugin_p = mock.patch('neutron.manager.NeutronManager.get_plugin')
|
||||
get_plugin = plugin_p.start()
|
||||
get_plugin.return_value = plugin
|
||||
service_plugin_p = mock.patch(
|
||||
'neutron.manager.NeutronManager.get_service_plugins')
|
||||
get_service_plugin = service_plugin_p.start()
|
||||
get_service_plugin.return_value = {constants.L3_ROUTER_NAT: plugin}
|
||||
service_plugin = mock.Mock()
|
||||
service_plugin.get_l3_agents_hosting_routers.return_value = [l3_agent]
|
||||
self._fake_vpn_router_id = _uuid()
|
||||
service_plugin._get_vpnservice.return_value = {
|
||||
'router_id': self._fake_vpn_router_id
|
||||
}
|
||||
self.driver = vyatta_ipsec.VyattaIPsecDriver(service_plugin)
|
||||
|
||||
def _test_update(self, func, args, additional_info=None):
|
||||
ctxt = n_ctx.Context('', 'somebody')
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver.agent_rpc.client, 'cast'),
|
||||
mock.patch.object(self.driver.agent_rpc.client, 'prepare'),
|
||||
) as (
|
||||
rpc_mock, prepare_mock
|
||||
):
|
||||
prepare_mock.return_value = self.driver.agent_rpc.client
|
||||
func(ctxt, *args)
|
||||
|
||||
prepare_args = {'server': 'fake_host', 'version': '1.0'}
|
||||
prepare_mock.assert_called_once_with(**prepare_args)
|
||||
|
||||
rpc_mock.assert_called_once_with(ctxt, 'vpnservice_updated',
|
||||
**additional_info)
|
||||
|
||||
def test_create_ipsec_site_connection(self):
|
||||
self._test_update(self.driver.create_ipsec_site_connection,
|
||||
[FAKE_VPN_CONNECTION],
|
||||
{'router': {'id': self._fake_vpn_router_id}})
|
||||
|
||||
def test_update_ipsec_site_connection(self):
|
||||
self._test_update(self.driver.update_ipsec_site_connection,
|
||||
[FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION],
|
||||
{'router': {'id': self._fake_vpn_router_id}})
|
||||
|
||||
def test_delete_ipsec_site_connection(self):
|
||||
self._test_update(self.driver.delete_ipsec_site_connection,
|
||||
[FAKE_VPN_CONNECTION],
|
||||
{'router': {'id': self._fake_vpn_router_id}})
|
||||
|
||||
def test_update_vpnservice(self):
|
||||
self._test_update(self.driver.update_vpnservice,
|
||||
[FAKE_VPN_SERVICE, FAKE_VPN_SERVICE],
|
||||
{'router': {'id': FAKE_VPN_SERVICE['router_id']}})
|
||||
|
||||
def test_delete_vpnservice(self):
|
||||
self._test_update(self.driver.delete_vpnservice,
|
||||
[FAKE_VPN_SERVICE],
|
||||
{'router': {'id': FAKE_VPN_SERVICE['router_id']}})
|
@ -0,0 +1,52 @@
|
||||
# Copyright 2015 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 neutron.agent.common import config as agent_config
|
||||
from neutron.openstack.common import uuidutils
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron_vpnaas.services.vpn import vyatta_vpn_service
|
||||
from neutron_vpnaas.tests import base
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
FAKE_ROUTER_ID = _uuid()
|
||||
|
||||
|
||||
class TestVyattaVPNService(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVyattaVPNService, self).setUp()
|
||||
self.conf = cfg.CONF
|
||||
agent_config.register_root_helper(self.conf)
|
||||
self.ri_kwargs = {'root_helper': self.conf.AGENT.root_helper,
|
||||
'agent_conf': self.conf,
|
||||
'interface_driver': mock.sentinel.interface_driver}
|
||||
self.agent = mock.Mock()
|
||||
self.vyatta_service = vyatta_vpn_service.VyattaVPNService.instance(
|
||||
self.agent)
|
||||
self.l3_agent = self.vyatta_service.l3_agent
|
||||
|
||||
def test_get_router_client(self):
|
||||
self.vyatta_service.get_router_client(FAKE_ROUTER_ID)
|
||||
|
||||
self.l3_agent.get_router_client.assert_called_once_with(FAKE_ROUTER_ID)
|
||||
|
||||
def test_get_router(self):
|
||||
self.vyatta_service.get_router(FAKE_ROUTER_ID)
|
||||
|
||||
self.l3_agent.get_router.assert_called_once_with(FAKE_ROUTER_ID)
|
@ -38,6 +38,7 @@ console_scripts =
|
||||
device_drivers =
|
||||
neutron.services.vpn.device_drivers.ipsec.OpenSwanDriver = neutron_vpnaas.services.vpn.device_drivers.ipsec:OpenSwanDriver
|
||||
neutron.services.vpn.device_drivers.cisco_ipsec.CiscoCsrIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.cisco_ipsec:CiscoCsrIPsecDriver
|
||||
neutron.services.vpn.device_drivers.vyatta_ipsec.VyattaIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.vyatta_ipsec:VyattaIPsecDriver
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user