VPN as a Service (VPNaaS) Agent

This is the iteration of the VPNaaS Agent with some basic
functionality to enable integration of Plugin - Agent - Driver.

Co-Authored-By: Van Hung Pham <hungpv@vn.fujitsu.com>
Change-Id: I0b86c432e4b2210e5f2a73a7e3ba16d10467f0f2
Closes-Bug: 1692128
This commit is contained in:
Cao Xuan Hoang 2017-07-28 08:10:16 +07:00
parent e3da5c10c4
commit 99d2687b83
12 changed files with 123 additions and 178 deletions

16
devstack/lib/l3_agent Normal file
View File

@ -0,0 +1,16 @@
# This file is completely based on one in the neutron repository here:
# http://git.openstack.org/cgit/openstack/neutron/tree/devstack/lib/l2_agent
function plugin_agent_add_l3_agent_extension {
local l3_agent_extension=$1
if [[ -z "$L3_AGENT_EXTENSIONS" ]]; then
L3_AGENT_EXTENSIONS=$l3_agent_extension
elif [[ ! ,${L3_AGENT_EXTENSIONS}, =~ ,${l3_agent_extension}, ]]; then
L3_AGENT_EXTENSIONS+=",$l3_agent_extension"
fi
}
function configure_l3_agent {
iniset $Q_L3_CONF_FILE AGENT extensions "$L3_AGENT_EXTENSIONS"
}

View File

@ -3,6 +3,10 @@
VPNAAS_XTRACE=$(set +o | grep xtrace)
set -o xtrace
# Source L3 agent extension management
LIBDIR=$DEST/neutron-vpnaas/devstack/lib
source $LIBDIR/l3_agent
function neutron_vpnaas_install {
setup_develop $NEUTRON_VPNAAS_DIR
if is_service_enabled q-l3; then
@ -31,45 +35,24 @@ function neutron_vpnaas_configure_common {
iniadd $NEUTRON_VPNAAS_CONF service_providers service_provider $NEUTRON_VPNAAS_SERVICE_PROVIDER
}
function neutron_vpnaas_configure_db {
$NEUTRON_BIN_DIR/neutron-db-manage --subproject neutron-vpnaas --config-file $NEUTRON_CONF --config-file /$Q_PLUGIN_CONF_FILE upgrade head
}
function neutron_vpnaas_configure_agent {
local conf_file=${1:-$Q_VPN_CONF_FILE}
cp $NEUTRON_VPNAAS_DIR/etc/vpn_agent.ini.sample $conf_file
plugin_agent_add_l3_agent_extension vpnaas
configure_l3_agent
if [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then
if is_fedora; then
iniset_multiline $conf_file vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.fedora_strongswan_ipsec.FedoraStrongSwanDriver
iniset_multiline $Q_L3_CONF_FILE vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.fedora_strongswan_ipsec.FedoraStrongSwanDriver
else
iniset_multiline $conf_file vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver
iniset_multiline $Q_L3_CONF_FILE vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver
fi
elif [[ "$IPSEC_PACKAGE" == "libreswan" ]]; then
iniset_multiline $Q_VPN_CONF_FILE vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.libreswan_ipsec.LibreSwanDriver
iniset_multiline $Q_L3_CONF_FILE vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.libreswan_ipsec.LibreSwanDriver
else
iniset_multiline $conf_file vpnagent vpn_device_driver $NEUTRON_VPNAAS_DEVICE_DRIVER
iniset_multiline $Q_L3_CONF_FILE vpnagent vpn_device_driver $NEUTRON_VPNAAS_DEVICE_DRIVER
fi
}
function neutron_vpnaas_start {
local cfg_file
local opts="--config-file $NEUTRON_CONF --config-file=$Q_L3_CONF_FILE --config-file=$Q_VPN_CONF_FILE"
for cfg_file in ${Q_VPN_EXTRA_CONF_FILES[@]}; do
opts+=" --config-file $cfg_file"
done
run_process neutron-vpnaas "$AGENT_VPN_BINARY $opts"
}
function neutron_vpnaas_stop {
local ipsec_data_dir=$DATA_DIR/neutron/ipsec
local pids
if [ -d $ipsec_data_dir ]; then
pids=$(find $ipsec_data_dir -name 'pluto.pid' -exec cat {} \;)
fi
if [ -n "$pids" ]; then
sudo kill $pids
fi
stop_process neutron-vpnaas
function neutron_vpnaas_configure_db {
$NEUTRON_BIN_DIR/neutron-db-manage --subproject neutron-vpnaas --config-file $NEUTRON_CONF --config-file /$Q_PLUGIN_CONF_FILE upgrade head
}
function neutron_vpnaas_generate_config_files {
@ -97,17 +80,6 @@ elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
neutron_vpnaas_configure_agent
fi
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
if is_service_enabled q-l3; then
echo_summary "Initializing neutron-vpnaas"
neutron_vpnaas_start
fi
elif [[ "$1" == "unstack" ]]; then
if is_service_enabled q-l3; then
neutron_vpnaas_stop
fi
# NOP for clean step
fi

View File

@ -1,9 +1,5 @@
# Settings for the VPNaaS devstack plugin
enable_service neutron-vpnaas
AGENT_VPN_BINARY="$NEUTRON_BIN_DIR/neutron-vpn-agent"
# Plugin
VPN_PLUGIN=${VPN_PLUGIN:-"vpnaas"}
@ -17,9 +13,6 @@ NEUTRON_VPNAAS_DEVICE_DRIVER=${NEUTRON_VPNAAS_DEVICE_DRIVER:-"neutron_vpnaas.ser
# Config files
NEUTRON_CONF_DIR=${NEUTRON_CONF_DIR:-"/etc/neutron"}
NEUTRON_VPNAAS_DIR=$DEST/neutron-vpnaas
Q_VPN_CONF_FILE=$NEUTRON_CONF_DIR/vpn_agent.ini
NEUTRON_VPNAAS_CONF_FILE=neutron_vpnaas.conf
NEUTRON_VPNAAS_CONF=$NEUTRON_CONF_DIR/$NEUTRON_VPNAAS_CONF_FILE
declare -a Q_VPN_EXTRA_CONF_FILES

View File

@ -1,17 +0,0 @@
# 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 agent
def main():
agent.main()

View File

@ -1,4 +1,5 @@
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
# Copyright 2017, Fujitsu Limited
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -14,13 +15,15 @@
# under the License.
from neutron.agent.l3 import agent as l3_agent
from neutron.agent import l3_agent as entry
from neutron_lib.agent import l3_extension
from oslo_config import cfg
from oslo_log import log as logging
from neutron_vpnaas._i18n import _
from neutron_vpnaas.services.vpn import vpn_service
LOG = logging.getLogger(__name__)
vpn_agent_opts = [
cfg.MultiStrOpt(
'vpn_device_driver',
@ -43,19 +46,52 @@ vpn_agent_opts = [
cfg.CONF.register_opts(vpn_agent_opts, 'vpnagent')
class VPNAgent(l3_agent.L3NATAgentWithStateReport):
"""VPNAgent class which can handle vpn service drivers."""
def __init__(self, host, conf=None):
super(VPNAgent, self).__init__(host=host, conf=conf)
self.agent_state['binary'] = 'neutron-vpn-agent'
self.service = vpn_service.VPNService(self)
self.device_drivers = self.service.load_device_drivers(host)
class VPNAgent(l3_extension.L3AgentExtension):
"""VPNaaS Agent support to be used by Neutron L3 agent."""
def process_state_change(self, router_id, state):
def initialize(self, connection, driver_type):
LOG.debug("Loading VPNaaS")
def consume_api(self, agent_api):
LOG.debug("Loading consume_api for VPNaaS")
self.agent_api = agent_api
def __init__(self, host, conf):
LOG.debug("Initializing VPNaaS agent")
self.agent_api = None
self.conf = conf
self.host = host
self.service = vpn_service.VPNService(self)
self.device_drivers = self.service.load_device_drivers(self.host)
def add_router(self, context, data):
"""Handles router add event"""
ri = self.agent_api.get_router_info(data['id'])
if ri is not None:
for device_driver in self.device_drivers:
device_driver.create_router(ri)
device_driver.sync(context, [ri.router])
else:
LOG.debug("Router %s was concurrently deleted while "
"creating VPN for it", data['id'])
def update_router(self, context, data):
"""Handles router update event"""
for device_driver in self.device_drivers:
device_driver.sync(context, [data])
def delete_router(self, context, data):
"""Handles router delete event"""
for device_driver in self.device_drivers:
device_driver.destroy_router(data)
def ha_state_change(self, context, data):
"""Enable the vpn process when router transitioned to master.
And disable vpn process for backup router.
And disable vpn process for backup router.
"""
router_id = data['router_id']
state = data['state']
for device_driver in self.device_drivers:
if router_id in device_driver.processes:
process = device_driver.processes[router_id]
@ -64,11 +100,13 @@ class VPNAgent(l3_agent.L3NATAgentWithStateReport):
else:
process.disable()
def enqueue_state_change(self, router_id, state):
"""Handle HA router state changes for vpn process"""
self.process_state_change(router_id, state)
super(VPNAgent, self).enqueue_state_change(router_id, state)
class L3WithVPNaaS(VPNAgent):
def main():
entry.main(manager='neutron_vpnaas.services.vpn.agent.VPNAgent')
def __init__(self, conf=None):
if conf:
self.conf = conf
else:
self.conf = cfg.CONF
super(L3WithVPNaaS, self).__init__(
host=self.conf.host, conf=self.conf)

View File

@ -14,9 +14,6 @@
# under the License.
from neutron.services import provider_configuration as provconfig
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from oslo_log import log as logging
from oslo_utils import importutils
@ -31,16 +28,7 @@ class VPNService(object):
"""VPN Service observer."""
def __init__(self, l3_agent):
"""Creates a VPN Service instance with context."""
# TODO(pc_m): Replace l3_agent argument with config, once none of the
# device driver implementations need the L3 agent.
self.conf = l3_agent.conf
registry.subscribe(
router_added_actions, resources.ROUTER, events.AFTER_CREATE)
registry.subscribe(
router_removed_actions, resources.ROUTER, events.BEFORE_DELETE)
registry.subscribe(
router_updated_actions, resources.ROUTER, events.AFTER_UPDATE)
def load_device_drivers(self, host):
"""Loads one or more device drivers for VPNaaS."""
@ -57,25 +45,3 @@ class VPNService(object):
raise vpnaas.DeviceDriverImportError(
device_driver=device_driver)
return drivers
def router_added_actions(resource, event, l3_agent, **kwargs):
"""Create the router and sync for each loaded device driver."""
router = kwargs['router']
for device_driver in l3_agent.device_drivers:
device_driver.create_router(router)
device_driver.sync(l3_agent.context, [router.router])
def router_removed_actions(resource, event, l3_agent, **kwargs):
"""Remove the router from each loaded device driver."""
router = kwargs['router']
for device_driver in l3_agent.device_drivers:
device_driver.destroy_router(router.router_id)
def router_updated_actions(resource, event, l3_agent, **kwargs):
"""Perform a sync on each loaded device driver."""
router = kwargs['router']
for device_driver in l3_agent.device_drivers:
device_driver.sync(l3_agent.context, [router.router])

View File

@ -13,11 +13,12 @@
import collections
import copy
import functools
import os
import mock
import netaddr
from neutron.agent.common import ovs_lib
from neutron.agent.l3 import agent as neutron_l3_agent
from neutron.agent.l3 import l3_agent_extensions_manager as ext_manager
from neutron.agent.l3 import namespaces as n_namespaces
from neutron.agent.l3 import router_info
from neutron.agent import l3_agent as l3_agent_main
@ -189,6 +190,7 @@ class SiteInfo(object):
'id': _uuid(),
'admin_state_up': True,
'network_id': _uuid(),
'mtu': 1500,
'mac_address': n_utils.get_random_mac(MAC_BASE),
'subnets': [
{
@ -211,6 +213,7 @@ class SiteInfo(object):
def generate_router_info(self):
self.info = copy.deepcopy(FAKE_ROUTER)
self.info['id'] = _uuid()
self.info['project_id'] = _uuid()
self.info['_interfaces'] = [
self._generate_private_interface_for_router(subnet)
for subnet in self.private_nets]
@ -281,13 +284,13 @@ class SiteInfoWithHaRouter(SiteInfo):
class TestIPSecBase(base.BaseSudoTestCase):
vpn_agent_ini = os.environ.get('VPN_AGENT_INI',
'/etc/neutron/vpn_agent.ini')
NESTED_NAMESPACE_SEPARATOR = '@'
def setUp(self):
super(TestIPSecBase, self).setUp()
mock.patch('neutron.agent.l3.agent.L3PluginApi').start()
mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.'
'IPsecVpnDriverApi').start()
# avoid report_status running periodically
mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall').start()
# Both the vpn agents try to use execute_rootwrap_daemon's socket
@ -306,7 +309,10 @@ class TestIPSecBase(base.BaseSudoTestCase):
# Can reproduce the exception in the test only
ip_lib.send_ip_addr_adv_notif = mock.Mock()
self.vpn_agent = self._configure_agent('agent1')
self.conf = self._configure_agent('agent1')
self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1',
self.conf)
self.vpn_agent = vpn_agent.L3WithVPNaaS(self.conf)
self.driver = self.vpn_agent.device_drivers[0]
self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock(
return_value=[])
@ -340,6 +346,7 @@ class TestIPSecBase(base.BaseSudoTestCase):
logging.register_options(config)
agent_config.register_process_monitor_opts(config)
ext_manager.register_opts(config)
return config
def _configure_agent(self, host):
@ -348,6 +355,7 @@ class TestIPSecBase(base.BaseSudoTestCase):
l3_agent_main.register_opts(config)
cfg.CONF.set_override('debug', True)
agent_config.setup_logging()
config.set_override('extensions', ['vpnaas'], 'agent')
config.set_override(
'interface_driver',
'neutron.agent.linux.interface.OVSInterfaceDriver')
@ -372,13 +380,11 @@ class TestIPSecBase(base.BaseSudoTestCase):
config.set_override('config_base_dir',
ipsec_config_base_dir, group='ipsec')
config(['--config-file', self.vpn_agent_ini])
# Assign ip address to br-ex port because it is a gateway
ex_port = ip_lib.IPDevice(br_ex.br_name)
ex_port.addr.add(str(PUBLIC_NET[1]))
return vpn_agent.VPNAgent(host, config)
return config
def _setup_failover_agent(self):
self.failover_agent = self._configure_agent('agent2')
@ -456,12 +462,12 @@ class TestIPSecBase(base.BaseSudoTestCase):
"""
if l3ha:
site = SiteInfoWithHaRouter(public_net, private_nets,
self.vpn_agent.host,
self.agent.host,
self.failover_agent.host)
else:
site = SiteInfo(public_net, private_nets)
site.router = self.create_router(self.vpn_agent, site.info)
site.router = self.create_router(self.agent, site.info)
if l3ha:
backup_info = site.generate_backup_router_info()
site.backup_router = self.create_router(self.failover_agent,
@ -503,6 +509,8 @@ class TestIPSecBase(base.BaseSudoTestCase):
peer_router_id = site2.router.router_id
self.driver.sync(mock.Mock(), [{'id': local_router_id},
{'id': peer_router_id}])
self.agent._process_updated_router(site1.router.router)
self.agent._process_updated_router(site2.router.router)
self.addCleanup(self.driver._delete_vpn_processes,
[local_router_id, peer_router_id], [])

View File

@ -16,6 +16,7 @@
import os
import mock
from neutron.agent.l3 import agent as neutron_l3_agent
from neutron.agent.l3 import legacy_router
from neutron.conf.agent.l3 import config as l3_config
from neutron.tests.functional import base
@ -23,6 +24,7 @@ from neutron_lib import constants
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron_vpnaas.services.vpn import agent as vpn_agent
from neutron_vpnaas.services.vpn.device_drivers import ipsec
from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec
from neutron_vpnaas.tests.functional.common import test_scenario
@ -167,6 +169,24 @@ class TestStrongSwanDeviceDriver(base.BaseSudoTestCase):
class TestStrongSwanScenario(test_scenario.TestIPSecBase):
def setUp(self):
super(TestStrongSwanScenario, self).setUp()
self.conf.register_opts(strongswan_ipsec.strongswan_opts,
'strongswan')
VPNAAS_STRONGSWAN_DEVICE = ('neutron_vpnaas.services.vpn.'
'device_drivers.strongswan_ipsec.'
'StrongSwanDriver')
cfg.CONF.set_override('vpn_device_driver',
[VPNAAS_STRONGSWAN_DEVICE],
'vpnagent')
self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1',
self.conf)
self.vpn_agent = vpn_agent.L3WithVPNaaS(self.conf)
vpn_service = mock.Mock()
vpn_service.conf = self.conf
self.driver = strongswan_ipsec.StrongSwanDriver(
vpn_service, host=mock.sentinel.host)
def _override_ikepolicy_for_site(self, site, ikepolicy):
ipsec_connection = site.vpn_service['ipsec_site_connections'][0]
ipsec_connection['ikepolicy'] = ikepolicy

View File

@ -14,7 +14,6 @@
# under the License.
import mock
from neutron.agent.l3 import legacy_router
from neutron_lib.callbacks import registry
from oslo_config import cfg
from oslo_utils import uuidutils
@ -90,43 +89,3 @@ class TestVirtualPrivateNetworkDeviceDriverLoading(VPNBaseTestCase):
'vpnagent')
self.assertRaises(vpnaas.DeviceDriverImportError,
self.service.load_device_drivers, 'host')
class TestVPNServiceEventHandlers(VPNBaseTestCase):
def setUp(self):
super(TestVPNServiceEventHandlers, self).setUp()
self.l3_agent = mock.Mock()
self.l3_agent.context = mock.sentinel.context
mock.patch.object(registry, 'subscribe').start()
self.service = vpn_service.VPNService(mock.Mock())
self.device_driver = mock.Mock()
self.l3_agent.device_drivers = [self.device_driver]
def test_router_added_actions(self):
ri = legacy_router.LegacyRouter(router_id=FAKE_ROUTER_ID,
agent=self.l3_agent,
**self.ri_kwargs)
vpn_service.router_added_actions(mock.Mock(), mock.Mock(),
self.l3_agent, router=ri)
self.device_driver.create_router.assert_called_once_with(ri)
self.device_driver.sync.assert_called_once_with(self.l3_agent.context,
[ri.router])
def test_router_removed_actions(self):
ri = legacy_router.LegacyRouter(router_id=FAKE_ROUTER_ID,
agent=self.l3_agent,
**self.ri_kwargs)
vpn_service.router_removed_actions(mock.Mock(), mock.Mock(),
self.l3_agent, router=ri)
self.device_driver.destroy_router.assert_called_once_with(
FAKE_ROUTER_ID)
def test_router_updated_actions(self):
ri = legacy_router.LegacyRouter(router_id=FAKE_ROUTER_ID,
agent=self.l3_agent,
**self.ri_kwargs)
vpn_service.router_updated_actions(mock.Mock(), mock.Mock(),
self.l3_agent, router=ri)
self.device_driver.sync.assert_called_once_with(self.l3_agent.context,
[ri.router])

View File

@ -32,8 +32,9 @@ setup-hooks =
[entry_points]
console_scripts =
neutron-vpn-netns-wrapper = neutron_vpnaas.services.vpn.common.netns_wrapper:main
neutron-vpn-agent = neutron_vpnaas.cmd.eventlet.agent:main
neutron-vyatta-agent = neutron_vpnaas.cmd.eventlet.vyatta_agent:main
neutron.agent.l3.extensions =
vpnaas = neutron_vpnaas.services.vpn.agent:L3WithVPNaaS
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

View File

@ -40,17 +40,6 @@ function _install_vpn_package {
neutron_agent_vpnaas_install_agent_packages
}
function _configure_vpn_ini_file {
echo_summary "Configuring VPN ini file"
local temp_ini=$(mktemp)
neutron_vpnaas_generate_config_files
neutron_vpnaas_configure_agent $temp_ini
sudo install -d -o $STACK_USER /etc/neutron/
sudo install -m 644 -o $STACK_USER $temp_ini $Q_VPN_CONF_FILE
}
function configure_host_for_vpn_func_testing {
echo_summary "Configuring for VPN functional testing"
if [ "$IS_GATE" == "True" ]; then
@ -60,7 +49,6 @@ function configure_host_for_vpn_func_testing {
# oslo-config-generator present (as this script runs before tox.ini).
sudo pip install --force oslo.config
_install_vpn_package
_configure_vpn_ini_file
}

View File

@ -55,4 +55,5 @@ fi
cp -p ${src_conf} ${dst_conf}
sed -i "s:^filters_path=.*$:filters_path=${dst_rootwrap_path}:" ${dst_conf}
sed -i "s:^\(exec_dirs=.*\)$:\1,${venv_path}/bin:" ${dst_conf}
sudo mkdir -p /etc/neutron/
sudo cp ${dst_conf} /etc/neutron/