From 88c0869d876efb81db03616191ede8ae56db3f9a Mon Sep 17 00:00:00 2001 From: Suresh Dharavath Date: Thu, 23 Jun 2016 23:36:47 +0530 Subject: [PATCH 1/2] asav vpn driver and perf changes --- devstack/lib/nfp | 4 +- .../drivers/vpn/vyos/test_vpn_driver.py | 11 +- .../configurator/test_data/vpn_test_data.py | 6 +- gbpservice/nfp/configurator/agents/vpn.py | 22 +- gbpservice/nfp/configurator/config/asav.conf | 2 +- .../drivers/firewall/asav/asav_fw_driver.py | 12 +- .../configurator/drivers/vpn/asav/__init__.py | 0 .../drivers/vpn/asav/asav_vpn_constants.py | 18 + .../drivers/vpn/asav/asav_vpn_driver.py | 1266 +++++++++++++++++ .../drivers/vpn/vyos/vyos_vpn_constants.py | 18 + .../drivers/vpn/vyos/vyos_vpn_driver.py | 101 +- .../nfp/configurator/lib/data_filter.py | 1 + .../lib/generic_config_constants.py | 2 +- .../nfp/configurator/lib/vpn_constants.py | 19 +- .../drivers/orchestration_driver.py | 3 + .../modules/device_orchestrator.py | 31 +- .../vpn/drivers/nfp_vpnaas_driver.py | 25 +- test-requirements.txt | 2 + 18 files changed, 1432 insertions(+), 111 deletions(-) create mode 100644 gbpservice/nfp/configurator/drivers/vpn/asav/__init__.py create mode 100644 gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_constants.py create mode 100644 gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_driver.py create mode 100644 gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_constants.py diff --git a/devstack/lib/nfp b/devstack/lib/nfp index 4385bf25a6..f05255fd7f 100644 --- a/devstack/lib/nfp +++ b/devstack/lib/nfp @@ -275,7 +275,6 @@ function create_ep_and_nsp { gbp external-segment-create --ip-version 4 --cidr $EXT_NET_CIDR/$EXT_NET_MASK --external-route destination=0.0.0.0/0,nexthop= --shared True --subnet_id=$subnet_id default gbp nat-pool-create --ip-version 4 --ip-pool $EXT_NET_CIDR/$EXT_NET_MASK --external-segment default --shared True default - gbp ep-create --external-segments default ext_connect gbp nsp-create --network-service-params type=ip_pool,name=vip_ip,value=nat_pool svc_mgmt_fip_policy } @@ -301,10 +300,11 @@ function create_nfp_gbp_resources { gbp service-profile-create --servicetype LOADBALANCER --insertion-mode l3 --shared True --service-flavor service_vendor=haproxy,device_type=nova --vendor NFP lb_profile gbp service-profile-create --shared True --vendor NFP --servicetype LOADBALANCERV2 --service-flavor service_vendor=haproxy_lbaasv2,device_type=nova,flavor=m1.small --insertion-mode l3 lbv2_profile gbp service-profile-create --servicetype FIREWALL --insertion-mode l3 --shared True --service-flavor service_vendor=vyos,device_type=nova --vendor NFP vyos_fw_profile - gbp service-profile-create --servicetype VPN --insertion-mode l3 --shared True --service-flavor service_vendor=vyos,device_type=nova --vendor NFP vpn_profile + gbp service-profile-create --servicetype VPN --insertion-mode l3 --shared True --service-flavor service_vendor=vyos,device_type=nova --vendor NFP vyos_vpn_profile if [[ $DEVSTACK_MODE = enterprise ]]; then gbp service-profile-create --servicetype FIREWALL --insertion-mode l3 --shared True --service-flavor service_vendor=asav,device_type=nova --vendor NFP asav_fw_profile + gbp service-profile-create --servicetype VPN --insertion-mode l3 --shared True --service-flavor service_vendor=asav,device_type=nova --vendor NFP asav_vpn_profile fi create_ext_net create_ep_and_nsp diff --git a/gbpservice/neutron/tests/unit/nfp/configurator/drivers/vpn/vyos/test_vpn_driver.py b/gbpservice/neutron/tests/unit/nfp/configurator/drivers/vpn/vyos/test_vpn_driver.py index 9c5ff098eb..8eb8ae2542 100644 --- a/gbpservice/neutron/tests/unit/nfp/configurator/drivers/vpn/vyos/test_vpn_driver.py +++ b/gbpservice/neutron/tests/unit/nfp/configurator/drivers/vpn/vyos/test_vpn_driver.py @@ -17,7 +17,11 @@ vpn_test_data from gbpservice.nfp.configurator.agents import vpn from gbpservice.nfp.configurator.drivers.base import base_driver +from gbpservice.nfp.configurator.drivers.vpn.vyos import ( + vyos_vpn_constants as const) from gbpservice.nfp.configurator.drivers.vpn.vyos import vyos_vpn_driver + +from oslo_config import cfg from oslo_serialization import jsonutils import json @@ -148,12 +152,13 @@ class VpnGenericConfigDriverTestCase(unittest.TestCase): def __init__(self, *args, **kwargs): super(VpnGenericConfigDriverTestCase, self).__init__(*args, **kwargs) - self.conf = 'conf' + with mock.patch.object(cfg, 'CONF') as mock_cfg: + mock_cfg.configure_mock() + self.driver = vyos_vpn_driver.VpnaasIpsecDriver(mock_cfg) self.dict_objects = vpn_test_data.VPNTestData() self.context = self.dict_objects.make_service_context() self.plugin_rpc = vpn.VpnaasRpcSender(self.dict_objects.sc) self.rest_apt = vyos_vpn_driver.RestApi(self.dict_objects.vm_mgmt_ip) - self.driver = vyos_vpn_driver.VpnGenericConfigDriver(self.conf) self.resp = mock.Mock() self.fake_resp_dict = {'status': True} self.kwargs = self.dict_objects.fake_resource_data() @@ -308,7 +313,7 @@ def __init__(self, *args, **kwargs): self.dict_objects = vpn_test_data.VPNTestData() self.args = {'peer_address': '1.103.2.2'} self.fake_resp_dict = {'status': None} - self.timeout = 90 + self.timeout = const.REST_TIMEOUT self.data = {'data': 'data'} self.j_data = jsonutils.dumps(self.data) diff --git a/gbpservice/neutron/tests/unit/nfp/configurator/test_data/vpn_test_data.py b/gbpservice/neutron/tests/unit/nfp/configurator/test_data/vpn_test_data.py index 0723997441..83f7571850 100644 --- a/gbpservice/neutron/tests/unit/nfp/configurator/test_data/vpn_test_data.py +++ b/gbpservice/neutron/tests/unit/nfp/configurator/test_data/vpn_test_data.py @@ -13,7 +13,9 @@ """ Implements fake objects for assertion. """ -import json + +from gbpservice.nfp.configurator.drivers.vpn.vyos import ( + vyos_vpn_constants as const) class VPNTestData(object): @@ -76,7 +78,7 @@ def __init__(self): self.data__ = {"local_cidr": "11.0.6.0/24", "peer_address": "1.103.2.2", "peer_cidr": "141.0.0.0/24"} - self.timeout = 90 + self.timeout = const.REST_TIMEOUT self.ipsec_vpn_create = ['fip=192.168.20.75', 'tunnel_local_cidr=11.0.6.0/24', diff --git a/gbpservice/nfp/configurator/agents/vpn.py b/gbpservice/nfp/configurator/agents/vpn.py index 6b9e6b302f..c9a0e2c652 100644 --- a/gbpservice/nfp/configurator/agents/vpn.py +++ b/gbpservice/nfp/configurator/agents/vpn.py @@ -18,10 +18,9 @@ from gbpservice.nfp.configurator.lib import data_filter from gbpservice.nfp.configurator.lib import utils from gbpservice.nfp.configurator.lib import vpn_constants as const -from gbpservice.nfp.core import controller as main -from gbpservice.nfp.core.event import Event -from gbpservice.nfp.core import module as nfp_api +from gbpservice.nfp.core import event as main from gbpservice.nfp.core import log as nfp_logging +from gbpservice.nfp.core import module as nfp_api import oslo_messaging as messaging @@ -181,9 +180,9 @@ def __init__(self, sc, drivers): self._drivers = drivers self._plugin_rpc = VpnaasRpcSender(self._sc) - def _get_driver(self): + def _get_driver(self, service_vendor): - driver_id = const.SERVICE_TYPE + const.SERVICE_VENDOR + driver_id = const.SERVICE_TYPE + service_vendor return self._drivers[driver_id] def handle_event(self, ev): @@ -204,8 +203,10 @@ def handle_event(self, ev): % (os.getpid(), ev.id, const.VPN_GENERIC_CONFIG_RPC_TOPIC)) LOG.debug(msg) - - driver = self._get_driver() + service_vendor = ( + ev.data['context']['agent_info']['service_vendor']) + driver = self._get_driver(service_vendor) + setattr(VPNaasEventHandler, "service_driver", driver) self._vpnservice_updated(ev, driver) except Exception as err: msg = ("Failed to perform the operation: %s. %s" @@ -278,9 +279,8 @@ def _sync_ipsec_conns(self, context, svc_context): Returns: None """ try: - self._get_driver() - return self._get_driver().check_status(context, svc_context) + return self.service_driver.check_status(context, svc_context) except Exception as err: msg = ("Failed to sync ipsec connection information. %s." % str(err).capitalize()) @@ -316,9 +316,9 @@ def events_init(sc, drivers): Returns: None """ evs = [ - Event(id='VPNSERVICE_UPDATED', + main.Event(id='VPNSERVICE_UPDATED', handler=VPNaasEventHandler(sc, drivers)), - Event(id='VPN_SYNC', + main.Event(id='VPN_SYNC', handler=VPNaasEventHandler(sc, drivers))] sc.register_events(evs) diff --git a/gbpservice/nfp/configurator/config/asav.conf b/gbpservice/nfp/configurator/config/asav.conf index d0a6fd31ba..f9b24b16d2 100644 --- a/gbpservice/nfp/configurator/config/asav.conf +++ b/gbpservice/nfp/configurator/config/asav.conf @@ -11,7 +11,7 @@ # strictly forbidden unless prior written permission is obtained from # One Convergence, Inc., USA -[ASAV_FW_CONFIG] +[ASAV_CONFIG] # Username for ASAv Service VM mgmt_username = admin diff --git a/gbpservice/nfp/configurator/drivers/firewall/asav/asav_fw_driver.py b/gbpservice/nfp/configurator/drivers/firewall/asav/asav_fw_driver.py index b5b770c422..36eeb1f6ee 100644 --- a/gbpservice/nfp/configurator/drivers/firewall/asav/asav_fw_driver.py +++ b/gbpservice/nfp/configurator/drivers/firewall/asav/asav_fw_driver.py @@ -285,7 +285,7 @@ def configure_interfaces(self, context, resource_data): stitching_intf_name = self._get_device_interface_name( stitching_cidr) - security_level = self.conf.ASAV_FW_CONFIG.security_level + security_level = self.conf.ASAV_CONFIG.security_level commands = self._get_interface_commands( provider_intf_name, str(provider_interface_position), provider_ip, provider_mask, security_level, @@ -591,8 +591,8 @@ def __init__(self, conf): self.timeout = const.REST_TIMEOUT self.rest_api = RestApi(self.timeout) self.port = const.CONFIGURATION_SERVER_PORT - self.auth = HTTPBasicAuth(self.conf.ASAV_FW_CONFIG.mgmt_username, - self.conf.ASAV_FW_CONFIG.mgmt_userpass) + self.auth = HTTPBasicAuth(self.conf.ASAV_CONFIG.mgmt_username, + self.conf.ASAV_CONFIG.mgmt_userpass) super(FwaasDriver, self).__init__() def register_config_options(self): @@ -602,7 +602,7 @@ def register_config_options(self): """ - self.conf.register_opts(asav_auth_opts, 'ASAV_FW_CONFIG') + self.conf.register_opts(asav_auth_opts, 'ASAV_CONFIG') def get_rules(self, firewall, interface): """ Prepares ASAv specific firewall rules from the @@ -968,10 +968,10 @@ def _check_for_implicit_deny(self, firewall, interface): rules = firewall["firewall_rule_list"] if not rules: return self._get_deny_rule(interface) - elif (not self.conf.ASAV_FW_CONFIG.scan_all_rule and + elif (not self.conf.ASAV_CONFIG.scan_all_rule and rules[0]['description'].lower() == const.IMPLICIT_DENY): return self._get_deny_rule(interface) - elif self.conf.ASAV_FW_CONFIG.scan_all_rule: + elif self.conf.ASAV_CONFIG.scan_all_rule: for rule in rules: if rule['description'].lower() == const.IMPLICIT_DENY: return self._get_deny_rule(interface) diff --git a/gbpservice/nfp/configurator/drivers/vpn/asav/__init__.py b/gbpservice/nfp/configurator/drivers/vpn/asav/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_constants.py b/gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_constants.py new file mode 100644 index 0000000000..883340c74a --- /dev/null +++ b/gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_constants.py @@ -0,0 +1,18 @@ +# 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. + + +REQUEST_URL = "https://%s%s" +CONFIGURATION_SERVER_PORT = '443' +SERVICE_VENDOR = 'asav' + +REST_TIMEOUT = 160 diff --git a/gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_driver.py b/gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_driver.py new file mode 100644 index 0000000000..7ceaf960cf --- /dev/null +++ b/gbpservice/nfp/configurator/drivers/vpn/asav/asav_vpn_driver.py @@ -0,0 +1,1266 @@ +# 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 ipaddr +import requests +import time + +from oslo_concurrency import lockutils +from oslo_config import cfg +from oslo_serialization import jsonutils + +from requests.auth import HTTPBasicAuth + +from socket import inet_ntoa +from struct import pack + +from gbpservice.nfp.configurator.drivers.base import base_driver +from gbpservice.nfp.configurator.drivers.vpn.asav import ( + asav_vpn_constants as const) +from gbpservice.nfp.configurator.lib import constants as common_const +from gbpservice.nfp.configurator.lib import vpn_constants as vpn_const +from gbpservice.nfp.core import log as nfp_logging + +from neutron_lib import exceptions + +LOG = nfp_logging.getLogger(__name__) + + +class UnknownReasonException(exceptions.NeutronException): + message = "Unsupported rpcreason '%(reason)s' from plugin " + + +class UnknownResourceException(exceptions.NeutronException): + message = "Unsupported resource '%(resource)s' from plugin " + + +class ResourceErrorState(exceptions.NeutronException): + message = "Resource '%(name)s' : '%(id)s' \ + went to error state, check log" + +asav_auth_opts = [ + cfg.StrOpt( + 'mgmt_username', + default='admin', + help=('ASAv management user name')), + cfg.StrOpt( + 'mgmt_userpass', + default='b2Nhc2F2dm0=', + help=('ASAv management user password')), + cfg.StrOpt( + 'throughput_level', + default='1G', + help='throughput level'), + cfg.StrOpt( + 'reg_token', + default='', + help='Token'), + cfg.StrOpt('security_level', + default='0', + help='interface security level for asav'), + cfg.StrOpt('radius_ip', + help=('IP of radius server')), + cfg.StrOpt('radius_secret', + default='secret', + help=('secret to talk to radius')), + cfg.BoolOpt('scan_all_rule', default=False, + help='Look for all rules in list for implicit deny') +] + +""" REST API wrapper class that provides POST method to +communicate with the Service VM. + +""" + + +class RestApi(object): + + def __init__(self, timeout): + self.timeout = timeout + self.content_header = {'Content-Type': 'application/json'} + + def post(self, url, data, auth_header, response_data_expected=None): + """ Invokes REST POST call to the Service VM. + + :param url: URL to connect. + :param data: data to be sent. + :param auth_header: Authorization content to securely + connect to the Service VM. + :param response_data_expected: If set, the REST call to the Service VM + returns the result of the call to the caller. + + Returns: SUCCESS/Error message/ Content of the POST response. + + """ + + try: + msg = ("Initiating a POST call to URL: %r " + "with data: %r." % (url, data)) + LOG.info(msg) + data = jsonutils.dumps(data) + resp = requests.post(url, data, + headers=self.content_header, verify=False, + auth=auth_header, timeout=self.timeout) + except requests.exceptions.SSLError as err: + msg = ("REST API POST request failed for ASAv. " + "URL: %r, Data: %r. Error: %r" % ( + url, data, str(err).capitalize())) + LOG.error(msg) + return msg + except Exception as err: + msg = ("Failed to issue POST call " + "to service. URL: %r, Data: %r. Error: %r" % + (url, data, str(err).capitalize())) + LOG.error(msg) + return msg + + try: + result = resp.json() + except ValueError as err: + msg = ("Unable to parse response, invalid JSON. URL: " + "%r. %r" % (url, str(err).capitalize())) + LOG.error(msg) + return msg + if resp.status_code not in common_const.SUCCESS_CODES: + msg = ("Successfully issued a POST call. However, the result " + "of the POST API is negative. URL: %r. Response code: %s." + "Result: %r." % (url, resp.status_code, result)) + LOG.warning(msg) + return msg + msg = ("Successfully issued a POST call and the result of " + "the API operation is positive. URL: %r. Result: %r. " + "Status Code: %r." % (url, result, resp.status_code)) + LOG.info(msg) + return ( + common_const.STATUS_SUCCESS + if not response_data_expected + else dict(GET_RESPONSE=result)) + + def get(self, url, auth_header): + + try: + resp = requests.get(url, headers=self.content_header, + verify=False, auth=auth_header, + timeout=self.timeout) + except requests.exceptions.SSLError as err: + msg = ("REST API GET request failed for ASAv. " + "URL: %r, Error: %r" % ( + url, str(err).capitalize())) + LOG.error(msg) + return resp + except Exception as err: + msg = ("Failed to issue GET call " + "to service. URL: %r, Error: %r" % + (url, str(err).capitalize())) + LOG.error(msg) + return resp + + try: + result = resp.json() + except ValueError as err: + msg = ("Unable to parse response, invalid JSON. URL: " + "%r. %r" % (url, str(err).capitalize())) + LOG.error(msg) + return resp + if resp.status_code not in common_const.SUCCESS_CODES: + msg = ("Successfully issued a GET call. However, the result " + "of the GET API is negative. URL: %r. Response code: %s." + "Result: %r." % (url, resp.status_code, result)) + LOG.error(msg) + return resp + msg = ("Successfully issued a GET call and the result of " + "the API operation is positive. URL: %r. Result: %r. " + "Status Code: %r." % (url, result, resp.status_code)) + LOG.info(msg) + return resp + + +class VPNServiceValidator(object): + def __init__(self, agent): + self.agent = agent + + def _update_service_status(self, vpnsvc, status): + """ + Driver will call this API to report + status of VPN service. + """ + msg = ("Driver informing status: %s." + % status) + LOG.debug(msg) + vpnsvc_status = [{ + 'id': vpnsvc['id'], + 'status': status, + 'updated_pending_status':True}] + return vpnsvc_status + + def _error_state(self, context, vpnsvc): + self.agent.update_status( + context, self._update_service_status(vpnsvc, + vpn_const.STATE_ERROR)) + raise ResourceErrorState(name='vpn_service', id=vpnsvc['id']) + + def _active_state(self, context, vpnsvc): + self.agent.update_status( + context, self._update_service_status(vpnsvc, + vpn_const.STATE_ACTIVE)) + + def _get_local_cidr(self, vpn_svc): + svc_desc = vpn_svc['description'] + tokens = svc_desc.split(';') + local_cidr = tokens[1].split('=')[1] + return local_cidr + + def validate(self, context, vpnsvc): + lcidr = self._get_local_cidr(vpnsvc) + """ + Get the vpn services for this tenant + Check for overlapping lcidr - not allowed + """ + filters = {'tenant_id': [context['tenant_id']]} + t_vpnsvcs = self.agent.get_vpn_services( + context, filters=filters) + t_vpnsvcs.remove(vpnsvc) + for svc in t_vpnsvcs: + t_lcidr = self._get_local_cidr(svc) + if t_lcidr == lcidr: + self._error_state( + context, + vpnsvc) + + self._active_state(context, vpnsvc) + +""" vpn generic configuration driver for handling device +configuration requests. + +""" + + +class VPNGenericConfigDriver(base_driver.BaseDriver): + + def __init__(self): + pass + + def generic_configure_bulk_cli(self, mgmt_ip, commands, + response_data_expected=False): + """ Prepares the set of commands in such a way so that it issues + a bulk REST call to ASAv. + + Bulk REST call can contain all those commands that can + be input through CLI. + + :param mgmt_ip: Management IP address of the Service VM + :param commands: list of commands + :param response_data_expected: If set, the REST call to the Service VM + returns the result of the call to the caller. + + Returns: Result of the REST call. + + """ + + resource_uri = "/api/cli" + url = const.REQUEST_URL % (mgmt_ip, resource_uri) + + if not response_data_expected or not commands: + commands.append('write memory') + data = {"commands": commands} + + return self.rest_api.post(url, data, self.auth, response_data_expected) + + def _get_interface_commands(self, interface_name, interface_index, + interface_ip, interface_net_mask, + security_level, + asav_interface_type='gigabitEthernet', + mac_address=None): + """ Calculates the interface position. + + :param interface_name: Name of the interface + :param interface_index: Position of the interface + :param interface_ip: IP address of the interface + :param interface_net_mask: Subnet mask of the interface + :param security_level: Security level to be associated with + the interface + :param asav_interface_type: Management/Gigabitethernet + :param mac_address: MAC address of the interface + + Returns: A list of commands to configure interface. + + """ + + commands = list() + commands.append("interface " + asav_interface_type + " 0/" + + interface_index) + commands.append("nameif " + interface_name) + commands.append("security-level " + security_level) + command = "ip address " + interface_ip + " " + interface_net_mask + + commands.append(command) + commands.append("no shutdown") + + allow_inter_interface_traffic = ("same-security-traffic permit " + "inter-interface") + commands.append(allow_inter_interface_traffic) + return commands + + def get_interface_position(self, mgmt_ip, mac): + """ Calculates the interface position. + + :param mgmt_ip: Management IP address of the Service VM + :param mac: MAC address of the VM for which the + interface position has to be found. + + Returns: interface index. + + """ + + commands = list() + + commands.append("sh inte") + result = self.generic_configure_bulk_cli(mgmt_ip, commands, + response_data_expected=True) + + if (type(result) is dict) and result.get('GET_RESPONSE'): + data = ''.join(result['GET_RESPONSE']['response']).split( + 'GigabitEthernet0/') + for item in data: + if mac in item: + return item[0] + msg = ("Failed to retrieve interface position. Response: %r." % result) + raise Exception(msg) + + def _get_device_interface_name(self, cidr): + """ Prepares the interface name. + + :param cidr: CIDR of the interface + + Returns: interface name. + + """ + + return 'interface-' + cidr.replace('/', '_') + + def configure_interfaces(self, context, resource_data): + """ Configures interfaces for the service VM. + + :param context: neutron context + :param resource_data: a dictionary of vpn rules and objects + send by neutron plugin + + Returns: SUCCESS/Failure message with reason. + + """ + + time.sleep(10) # wait time for the agent to come up + try: + mgmt_ip = resource_data['mgmt_ip'] + provider_ip = resource_data.get('provider_ip') + provider_cidr = resource_data.get('provider_cidr') + provider_mac = resource_data.get('provider_mac') + stitching_ip = resource_data.get('stitching_ip') + stitching_cidr = resource_data.get('stitching_cidr') + stitching_mac = resource_data.get('stitching_mac') + provider_interface_position = resource_data.get( + 'provider_interface_index') + stitching_interface_position = resource_data.get( + 'stitching_interface_index') + + (provider_mac, stitching_mac) = self.get_asav_macs( + [provider_mac, stitching_mac]) + + provider_interface_position = self.get_interface_position( + mgmt_ip, provider_mac) + stitching_interface_position = str(int( + provider_interface_position) + 1) + + provider_macs = [provider_mac] + stitching_macs = [stitching_mac] + except Exception as err: + msg = ("Failed to configure interfaces. Error: %r." % err) + LOG.error(msg) + raise Exception(msg) + + commands = list() + try: + provider_mask = str(ipaddr.IPv4Network(provider_cidr).netmask) + stitching_mask = str(ipaddr.IPv4Network(stitching_cidr).netmask) + + provider_intf_name = self._get_device_interface_name(provider_cidr) + stitching_intf_name = self._get_device_interface_name( + stitching_cidr) + + security_level = self.conf.ASAV_CONFIG.security_level + commands = self._get_interface_commands( + provider_intf_name, str(provider_interface_position), + provider_ip, provider_mask, security_level, + mac_address=provider_macs) + result = self.generic_configure_bulk_cli(mgmt_ip, commands) + if result is not common_const.STATUS_SUCCESS: + return result + + commands = self._get_interface_commands( + stitching_intf_name, str(stitching_interface_position), + stitching_ip, stitching_mask, security_level, + mac_address=stitching_macs) + result = self.generic_configure_bulk_cli(mgmt_ip, commands) + if result is not common_const.STATUS_SUCCESS: + msg = ("Failed to configure ASAv interfaces. Reason: %r" % + result) + LOG.error(msg) + else: + msg = ("Configure ASAv interfaces.") + LOG.info(msg) + return result + except Exception as err: + msg = ("Exception while configuring interface. " + "Reason: %s" % err) + LOG.error(msg) + raise Exception(err) + + def clear_interfaces(self, context, resource_data): + """ Clears interfaces of the service VM. + + :param context: neutron context + :param resource_data: a dictionary of vpn rules and objects + send by neutron plugin + + Returns: SUCCESS/Failure message with reason. + + """ + + try: + mgmt_ip = resource_data['mgmt_ip'] + provider_mac = resource_data['provider_mac'] + asav_provider_mac = self.get_asav_mac(provider_mac) + + provider_interface_position = self.get_interface_position( + mgmt_ip, asav_provider_mac) + stitching_interface_position = str(int( + provider_interface_position) + 1) + + commands = [] + provider_interface_id = self._get_asav_interface_id( + provider_interface_position) + stitching_interface_id = self._get_asav_interface_id( + stitching_interface_position) + commands.append("clear configure interface " + + provider_interface_id) + commands.append("clear configure interface " + + stitching_interface_id) + result = self.generic_configure_bulk_cli(mgmt_ip, commands) + + if result is not common_const.STATUS_SUCCESS: + msg = ("Failed to clear ASAv interfaces. Reason: %r" % + result) + LOG.error(msg) + else: + msg = ("Cleared ASAv interfaces.") + LOG.info(msg) + return result + except Exception as err: + msg = ("Exception while clearing interface config. " + "Reason: %s" % err) + LOG.error(msg) + raise Exception(err) + + def configure_routes(self, context, resource_data): + """ Configure routes for the service VM. + + Issues REST call to service VM for configuration of routes. + + :param context: neutron context + :param resource_data: a dictionary of vpn rules and objects + send by neutron plugin + + Returns: SUCCESS/Failure message with reason. + + """ + + return self.configure_pbr_route(context, resource_data) + + def clear_routes(self, context, resource_data): + """ Clear routes for the service VM. + + Issues REST call to service VM for deletion of routes. + + :param context: neutron context + :param resource_data: a dictionary of vpn rules and objects + send by neutron plugin + + Returns: SUCCESS/Failure message with reason. + + """ + + return self.delete_pbr_route(context, resource_data) + + def _get_asav_interface_id(self, interface_position): + """ Prepares the interface id. + + :param interface_position: Position of the interface in the Service VM. + + Returns: physical interface name. + + """ + + return 'GigabitEthernet0/' + str(interface_position) + + def configure_pbr_route(self, context, resource_data): + """ Configure Policy Based routes for the service VM. + + Issues REST call to service VM for configuration of routes. + + :param context: neutron context + :param resource_data: a dictionary of vpn rules and objects + send by neutron plugin + + Returns: SUCCESS/Failure message with reason. + + """ + + mgmt_ip = resource_data['mgmt_ip'] + source_cidr = resource_data['source_cidrs'][0] + destination_cidr = resource_data['destination_cidr'] + gateway_ip = resource_data['gateway_ip'] + provider_mac = resource_data.get('provider_mac') + + source_network = str(ipaddr.IPv4Network(source_cidr).ip) + source_mask = str(ipaddr.IPv4Network(source_cidr).netmask) + asav_provider_mac = self.get_asav_mac(provider_mac) + + provider_interface_position = self.get_interface_position( + mgmt_ip, asav_provider_mac) + + interface_id = self._get_asav_interface_id(provider_interface_position) + permit_traffic_list = ['ip'] + commands = [] + try: + for protocol in permit_traffic_list: + commands.append("access-list pbracl%s extended permit %s %s" + " %s 0 0" % ( + source_cidr.replace('/', '_'), protocol, + source_network, source_mask)) + commands.append("route-map pbrmap%s permit 1" % ( + source_cidr.replace('/', '_'))) + commands.append("match ip address pbracl" + + source_cidr.replace('/', '_')) + commands.append("set ip next-hop " + gateway_ip) + commands.append("interface " + interface_id) + commands.append("policy-route route-map pbrmap%s" % ( + source_cidr.replace('/', '_'))) + + result = self.generic_configure_bulk_cli(mgmt_ip, commands) + if result is not common_const.STATUS_SUCCESS: + return result + + # Add interface based default ruote to stitching gw + dest_interface_name = self._get_device_interface_name( + destination_cidr) + adm_distance = int(provider_interface_position) + 2 + command = list() + command.append("route " + dest_interface_name + " 0 0 " + + gateway_ip + " " + str(adm_distance)) + dns_config = self._configure_dns(dest_interface_name) + command.extend(dns_config) + result = self.generic_configure_bulk_cli(mgmt_ip, command) + + if result is not common_const.STATUS_SUCCESS: + msg = ("Failed to configure ASAv routes. Reason: %r" % + result) + LOG.error(msg) + else: + msg = ("Configure ASAv routes.") + LOG.info(msg) + return result + + except Exception as err: + msg = ("Exception while configuring pbr route. " + "Reason: %s" % err) + LOG.error(msg) + raise Exception(err) + + def _configure_dns(self, dest_interface): + """ Prepares the command to configure DNS for the specified interface. + + :param dest_interface: Interface name + + Returns: a list of command. + + """ + + commands = [] + commands.append("dns domain-lookup %s" % dest_interface) + return commands + + def delete_pbr_route(self, context, resource_data): + """ Clears Policy Based routes for the service VM. + + Issues REST call to service VM for deletion of routes. + + :param context: neutron context + :param resource_data: a dictionary of vpn rules and objects + send by neutron plugin + + Returns: SUCCESS/Failure message with reason. + + """ + + mgmt_ip = resource_data['mgmt_ip'] + source_cidr = resource_data['source_cidrs'][0] + provider_mac = resource_data['provider_mac'] + try: + asav_provider_mac = self.get_asav_mac(provider_mac) + provider_interface_position = self.get_interface_position( + mgmt_ip, asav_provider_mac) + + commands = [] + interface_id = self._get_asav_interface_id( + provider_interface_position) + commands.append("interface " + interface_id) + commands.append("no policy-route route-map pbrmap" + + source_cidr.replace('/', '_')) + commands.append("no route-map pbrmap" + source_cidr.replace( + '/', '_')) + commands.append("clear configure access-list pbracl" + + source_cidr.replace('/', '_')) + commands.append("clear configure interface " + interface_id) + + self.generic_configure_bulk_cli(mgmt_ip, commands) + except Exception as err: + msg = ("Exception while deleting pbr route. " + "Reason: %s" % err) + LOG.error(msg) + return msg + else: + return common_const.STATUS_SUCCESS + + @staticmethod + def get_asav_mac(mac_addr): + """ Converts standard MAC address to ASAv format. + + :param mac_addr: MAC address + + Returns: ASAv MAC address. + + """ + + if not mac_addr: + raise Exception('Get asav mac received an empty mac address') + l = mac_addr.split(':') + asav_mac = "" + for i in range(0, len(l), 2): + asav_mac += (l[i] + l[i + 1] + ".") + + return asav_mac[:-1] + + @staticmethod + def get_asav_macs(mac_list): + """ Converts standard MAC address to ASAv format. + + :param mac_addr: A list of MAC addresses + + Returns: A tuples of ASAv MAC addresses. + + """ + asav_mac_list = list() + for mac_addr in mac_list: + if not mac_addr: + asav_mac_list.append(None) + continue + l = mac_addr.split(':') + asav_mac = "" + for i in range(0, len(l), 2): + asav_mac += (l[i] + l[i + 1] + ".") + asav_mac_list.append(asav_mac[:-1]) + + return tuple(asav_mac_list) + + +class VPNaasDriver(VPNGenericConfigDriver): + """ + vpn as a service driver for handling vpn + service configuration requests. + + We initialize service type and service vendor in this class because + agent loads class object only for those driver classes that have service + type and service vendor as class attributes. Also, only this driver + class is exposed to the agent. + + """ + service_type = vpn_const.SERVICE_TYPE + service_vendor = const.SERVICE_VENDOR + # history + # 1.0 Initial version + RPC_API_VERSION = '1.0' + + def __init__(self, conf): + self.conf = conf + self.port = const.CONFIGURATION_SERVER_PORT + self.register_config_options() + self.timeout = const.REST_TIMEOUT + self.rest_api = RestApi(self.timeout) + self.handlers = { + 'vpn_service': { + 'create': self.create_vpn_service}, + 'ipsec_site_connection': { + 'create': self.create_ipsec_conn, + 'update': self.update_ipsec_conn, + 'delete': self.delete_ipsec_conn}} + self.auth = HTTPBasicAuth(self.conf.ASAV_CONFIG.mgmt_username, + self.conf.ASAV_CONFIG.mgmt_userpass) + super(VPNaasDriver, self).__init__() + + def register_config_options(self): + """ Registers the config options. + + Returns: None + + """ + + self.conf.register_opts(asav_auth_opts, 'ASAV_CONFIG') + + def vpnservice_updated(self, context, resource_data): + """Handle VPNaaS service driver change notifications.""" + msg = "Handling VPN service update notification '%s'" % ( + resource_data.get('reason', '')) + LOG.debug(msg) + + resource = resource_data.get('resource') + tenant_id = resource['tenant_id'] + # Synchronize the update operation per tenant. + # Resources under tenant have inter dependencies. + + @lockutils.synchronized(tenant_id) + def _vpnservice_updated(context, resource_data): + reason = resource_data.get('reason') + rsrc = resource_data.get('rsrc_type') + + if rsrc not in self.handlers.keys(): + raise UnknownResourceException(resource=rsrc) + if reason not in self.handlers[rsrc].keys(): + raise UnknownReasonException(reason=reason) + + self.handlers[rsrc][reason](context, resource_data) + return _vpnservice_updated(context, resource_data) + + def _update_conn_status(self, conn, status): + """ + Driver will call this API to report + status of a connection - only if there is any change. + :param conn: ipsec conn dicitonary + :param status: status of the service. + + Returns: updated status dictionary + """ + msg = ("Driver informing connection status " + "changed to %s" % status) + LOG.debug(msg) + vpnsvc_status = [{ + 'id': conn['vpnservice_id'], + 'status':'ACTIVE', + 'updated_pending_status':False, + 'ipsec_site_connections':{ + conn['id']: { + 'status': status, + 'updated_pending_status': True}}}] + return vpnsvc_status + + def _error_state(self, context, conn): + self.agent.update_status( + context, self._update_conn_status(conn, + vpn_const.STATE_ERROR)) + raise ResourceErrorState( + name='ipsec-site-conn', + id=conn['id']) + + def _init_state(self, context, conn): + self.agent.update_status( + context, self._update_conn_status(conn, + vpn_const.STATE_INIT)) + + def _get_fip_from_vpnsvc(self, vpn_svc): + svc_desc = vpn_svc['description'] + tokens = svc_desc.split(';') + fip = tokens[0].split('=')[1] + return fip + + def _get_external_intf_name(self, vpn_svc): + svc_desc = vpn_svc['description'] + tokens = svc_desc.split(';') + stitching_cidr = tokens[5].split('=')[1] + return "interface-" + stitching_cidr.replace('/', '_') + + def _get_fip(self, svc_context): + return self._get_fip_from_vpnsvc(svc_context['service']) + + def _get_ipsec_tunnel_local_cidr_from_vpnsvc(self, vpn_svc): + svc_desc = vpn_svc['description'] + tokens = svc_desc.split(';') + tunnel_local_cidr = tokens[1].split('=')[1] + return tunnel_local_cidr + + def _get_ipsec_tunnel_local_cidr(self, svc_context): + # Provider PTG is local cidr for the tunnel + # - which is passed in svc description as of now + return self.\ + _get_ipsec_tunnel_local_cidr_from_vpnsvc( + svc_context['service']) + + def _ipsec_get_tenant_conns(self, context, conn, on_delete=False): + filters = { + 'tenant_id': [context['tenant_id']], + # 'vpnservice_id': [conn['vpnservice_id']], + 'peer_address': [conn['peer_address']]} + tenant_conns = self.agent.get_ipsec_conns( + context, filters) + if not tenant_conns: + if not on_delete: + # Something went wrong - atleast the current + # connection should be there + msg = "No tenant conns for filters (%s)" % (str(filters)) + LOG.error(msg) + # Move conn into err state + self._error_state(context, conn) + + if conn in tenant_conns: + tenant_conns.remove(conn) + if not tenant_conns: + return tenant_conns + + conn_list = [] + # get fip from connn description + mgmt_fip = self._get_fip_from_vpnsvc(conn) + svc_ids = [conn['vpnservice_id'] for conn in tenant_conns] + vpnservices = self.agent.get_vpn_services(context, ids=svc_ids) + copy_svc = copy.deepcopy(vpnservices) + # if service's fip matches new service's fip then both services + # lie on same instance, in this case we should only create tunnel + for vpn in copy_svc: + if mgmt_fip in vpn['description']: + continue + else: + vpnservices.remove(vpn) + # we have all the vpnservices matching on this fip + for vpn in vpnservices: + matching_conn = [conn for conn in tenant_conns + if conn['vpnservice_id'] == vpn['id']] + conn_list.extend(matching_conn) + if not on_delete: + # Remove the conns which are in pending_create + # state. It might be possible that more than one + # conns could get created in database before the rpc + # method of dev driver is invoked. + # We have to separate first conn creation from rest. + copy_conns = copy.deepcopy(conn_list) + for tconn in copy_conns: + if tconn['status'] == vpn_const.STATE_PENDING: + conn_list.remove(tconn) + + return conn_list + + def _ipsec_check_overlapping_peer(self, context, + tenant_conns, conn): + pcidrs = conn['peer_cidrs'] + for t_conn in tenant_conns: + t_pcidrs = t_conn['peer_cidrs'] + if conn['vpnservice_id'] != t_conn['vpnservice_id']: + continue + + for pcidr in pcidrs: + if pcidr in t_pcidrs: + msg = "Overlapping peer cidr (%s)" % (pcidr) + LOG.error(msg) + self._error_state( + context, conn) + + def create_vpn_service(self, context, resource_data): + svc = resource_data.get('resource') + validator = VPNServiceValidator(self.agent) + validator.validate(context, svc) + + def create_ipsec_conn(self, context, resource_data): + conn = resource_data.get('resource') + """ + Following conditions - + 0) Conn with more than one peer_address + is not allowed. This is because vyos has + conns and tunnels inside conn. But openstack + doesnt have tunnels. So conn will itslef need + to be mapped to tunnel. + a) Already conns exist for this tenant + . In this case just add a tunnel + . For same peer + . Add peer for different peer + b) First conn, create complete ipsec profile + """ + if len(conn['peer_cidrs']) < 1: + msg = "Invalid #of peer_cidrs can not be less than one" + LOG.error(msg) + self._error_state(context, conn) + + tenant_conns = self._ipsec_get_tenant_conns( + context, conn) + try: + if not tenant_conns: + self._ipsec_create_conn(context, conn) + else: + self._ipsec_create_conn(context, conn, same_peer=True) + except Exception as ex: + msg = "Configuring ipsec site conn failed Reason: %s" % ex + LOG.error(msg) + self._error_state(context, conn) + + def delete_ipsec_conn(self, context, resource_data): + conn = resource_data.get('resource') + tenant_conns = self._ipsec_get_tenant_conns( + context, conn, on_delete=True) + if tenant_conns: + self._ipsec_delete_connection( + context, conn, same_peer=True) + else: + self._ipsec_delete_connection( + context, conn) + + def update_ipsec_conn(self, context, resource_data): + # Talk to service manager and get floating ip + # (with tenant_id & svc_type as criteria) + # Might have to send some commands to + # update ipsec_conn params + # Can IPSEC policy params / IKE policy params + # be changed with connection intact ? + # Need to figure out which all params can be + # changed based on what vyos vm will support + # Maintain this resource ? will be useful in case of update ? + pass + + def _ipsec_create_tunnel(self, context, conn): + svc_context = self.agent.get_vpn_servicecontext( + context, self._get_filters(conn_id=conn['id']))[0] + + fip = self._get_fip(svc_context) + tunnel_local_cidr = self.\ + _get_ipsec_tunnel_local_cidr(svc_context) + + siteconn = svc_context['siteconns'][0]['connection'] + access_list = [] + for peer_cidr in siteconn['peer_cidrs']: + rules = self._configure_access_list(fip, tunnel_local_cidr, + peer_cidr, conn['id']) + access_list.extend(rules) + self._configure_bulk_cli(fip, access_list) + self._init_state(context, conn) + + def _ipsec_delete_tunnel(self, context, + vpnsvc, conn): + fip = self._get_fip_from_vpnsvc(vpnsvc) + tunnel_local_cidr = self._get_ipsec_tunnel_local_cidr_from_vpnsvc( + vpnsvc) + access_list = [] + for peer_cidr in conn['peer_cidrs']: + rules = self._configure_access_list(fip, tunnel_local_cidr, + peer_cidr, conn['id'], + delete=True) + access_list.extend(rules) + self._configure_bulk_cli(fip, access_list) + + def _ipsec_delete_connection(self, context, + conn, same_peer=False): + + commands = [] + fip = self._get_fip_from_vpnsvc(conn) + tfset_name = conn['ikepolicy_id'] + self.external_intf_name = self._get_external_intf_name(conn) + ipsecpolicy = {'id': conn['ipsecpolicy_id']} + siteconn = {'peer_address': conn['peer_address']} + tunnel_local_cidr = self._get_ipsec_tunnel_local_cidr_from_vpnsvc(conn) + access_list = [] + for peer_cidr in conn['peer_cidrs']: + rules = self._configure_access_list(fip, tunnel_local_cidr, + peer_cidr, conn['id'], + delete=True) + access_list.extend(rules) + ipsec = self._configure_ipsec( + fip, tfset_name, ipsecpolicy, conn, delete=True) + commands.extend(ipsec) + if not same_peer: + tunnelgroup = self._configure_tunnel_group(fip, siteconn, + delete=True) + commands.extend(tunnelgroup) + commands.extend(access_list) + '''commands.append("clear conf crypto ipsec ikev1 transform-set %s" % + tfset_name) + commands.append("sysopt connection permit-vpn") + commands.append("no crypto ikev1 enable %s" % self.external_intf_name) + commands.append("no sysopt connection permit-vpn")''' + try: + self._configure_bulk_cli(fip, commands) + except Exception as e: + msg = "Delete ipsec conn failed. Reason: %s" % e + LOG.warn(msg) + + def check_status(self, context, svc_context): + pass + + def _get_filters(self, tenant_id=None, vpnservice_id=None, conn_id=None, + peer_address=None): + filters = {} + if tenant_id: + filters['tenant_id'] = tenant_id + if vpnservice_id: + filters['vpnservice_id'] = vpnservice_id + if conn_id: + filters['siteconn_id'] = conn_id + if peer_address: + filters['peer_address'] = peer_address + return filters + + def _ipsec_create_conn(self, context, conn, same_peer=False): + svc_context = self.agent.get_vpn_servicecontext( + context, self._get_filters(conn_id=conn['id']))[0] + + fip = self._get_fip(svc_context) + tunnel_local_cidr = self.\ + _get_ipsec_tunnel_local_cidr(svc_context) + ikepolicy = svc_context['siteconns'][0]['ikepolicy'] + ipsecpolicy = svc_context['siteconns'][0]['ipsecpolicy'] + siteconn = svc_context['siteconns'][0]['connection'] + self.external_intf_name = self._get_external_intf_name( + svc_context['service']) + # TODO(kedar) shall this be in try-except? + ikepolicy_rest = self._configure_ikeconfig(fip, ikepolicy) + access_list = [] + for peer_cidr in siteconn['peer_cidrs']: + rules = self._configure_access_list(fip, tunnel_local_cidr, + peer_cidr, conn['id']) + access_list.extend(rules) + ipsec = self._configure_ipsec( + fip, ikepolicy['id'], ipsecpolicy, siteconn=siteconn) + # execute rest apis + commands = [] + # commands.append("route %s %s 255.255.255.255 %s 1" + # %(self.external_intf_name, + # siteconn['peer_address'], stitching_gw)) + commands.append("sysopt connection permit-vpn") + commands.extend(ikepolicy_rest) + commands.append("no sysopt connection permit-vpn") + commands.extend(access_list) + if not same_peer: + tunnelgroup = self._configure_tunnel_group(fip, siteconn) + commands.extend(tunnelgroup) + commands.extend(ipsec) + try: + self._configure_bulk_cli(fip, commands) + self._init_state(context, conn) + except Exception as ex: + rollback = [] + access_list = [] + msg = "Configuring ipsec failed, rolling back.: %s" % ex + LOG.error(msg) + try: + for peer_cidr in siteconn['peer_cidrs']: + rules = self._configure_access_list(fip, tunnel_local_cidr, + peer_cidr, conn['id'], + delete=True) + access_list.extend(rules) + ikepolicy_rest = self._configure_ikeconfig(fip, + ikepolicy, + delete=True) + tunnelgroup = self._configure_tunnel_group(fip, + siteconn, + delete=True) + ipsec = self._configure_ipsec( + fip, ikepolicy['id'], ipsecpolicy, siteconn, delete=True) + rollback.extend(ipsec) + rollback.extend(tunnelgroup) + rollback.extend(access_list) + # rollback.append("clear conf crypto ipsec ikev1 + # transform-set %s" % ikepolicy['id']) + # rollback.append("sysopt connection permit-vpn") + # rollback.append("no crypto ikev1 enable %s" % + # self.external_intf_name) + # rollback.append("no sysopt connection permit-vpn") + self._configure_bulk_cli(fip, rollback) + except Exception as ex: + msg = "Rollback ipsec failed. Reason: %s" % ex + LOG.warn(msg) + self._error_state(context, conn) + + def _get_ike_policies(self, mgmt_ip): + uri = "/api/vpn/ikev1policy" + url = const.REQUEST_URL % (mgmt_ip, uri) + resp = self.rest_api.get(url, self.auth) + return resp.json() + + def _correct_encryption_algo(self, algo): + algos = { + 'aes-128': "esp-aes", + 'aes-256': "esp-aes-256", + 'aes-192': "esp-aes-192", + '3des': "esp-3des", + 'des': "esp-des"} + return algos[algo] + + def _correct_auth_algo(self, algo): + algos = {'sha1': 'esp-sha-hmac', + 'md5': 'esp-md5-hmac'} + return algos[algo] + + def _configure_ikeconfig(self, fip, ikepolicy_req, delete=False): + dhgroup = {'group2': 'group 2', + 'group5': 'group 5', + 'group14': 'group 14'} + resp = self._get_ike_policies(fip) + commands = [] + policies = None + used_seq = [] + if resp.get('items'): + policies = resp['items'] + used_seq = [policy['priority'] for policy in policies] + if not used_seq: + seq_no = 1 + else: + for i in xrange(1, max(used_seq) + 2): + if i not in used_seq: + seq_no = i + break + + commands.append("crypto ikev1 policy %s" % str(seq_no)) + commands.append("authentication pre-share") + if ikepolicy_req['encryption_algorithm'] == 'aes-128': + asav_encryption_algorithm = 'aes' + else: + asav_encryption_algorithm = ikepolicy_req['encryption_algorithm'] + commands.append("encryption %s" % asav_encryption_algorithm) + auth_algorithm = None + if ikepolicy_req['auth_algorithm'] == 'sha1': + auth_algorithm = 'sha' + else: + auth_algorithm = ikepolicy_req['auth_algorithm'] + commands.append("hash %s" % auth_algorithm) + commands.append(dhgroup[ikepolicy_req['pfs']]) + if not delete: + commands.append("crypto ikev1 enable %s" % self.external_intf_name) + encrypt_algo = self._correct_encryption_algo( + ikepolicy_req['encryption_algorithm']) + auth_algo = self._correct_auth_algo( + ikepolicy_req['auth_algorithm']) + commands.append("crypto ipsec ikev1 transform-set %s %s %s" % ( + ikepolicy_req['id'], encrypt_algo, auth_algo)) + if delete: + commands = ["no " + command for command in commands] + return commands + + def _calculate_netmask(self, mask): + bits = 0xffffffff ^ (1 << 32 - int(mask)) - 1 + return inet_ntoa(pack('>I', bits)) + + def _configure_access_list(self, fip, local_cidr, peer_cidr, conn_id, + delete=False): + access_list = [] + name = conn_id.split('-')[0] + "-" + local_cidr.replace('/', '_') + try: + local = local_cidr.split('/') + peer = peer_cidr.split('/') + net_rule = (local[0] + ' ' + self._calculate_netmask(local[1]) + + ' ' + peer[0] + ' ' + self._calculate_netmask(peer[1])) + for protocol in ["ip"]: + rule = ("access-list %s extended permit %s %s" + % (name, protocol, net_rule)) + if delete: + rule = "no " + rule + access_list.append(rule) + except Exception as e: + msg = "Can not configure access list. Reason:%s" % e + LOG.error(msg) + raise e + return access_list + + def _configure_ipsec(self, fip, tfset_name, + ipsecpolicy=None, siteconn=None, + delete=False): + name = ipsecpolicy['id'] + commands = [] + if delete: + seq = self.get_delete_seqno(fip, siteconn['peer_address']) + # seq = 1 + if seq: + command = ["clear config crypto map %s %s" % (name, seq)] + commands.extend(command) + return commands + + access_list = (siteconn['id'].split('-')[0] + "-" + + self._get_ipsec_tunnel_local_cidr_from_vpnsvc( + siteconn).replace('/', '_')) + sequence = self._get_unique_sequenceno(fip) + prefix = "crypto map %s %s " % (name, sequence) + commands.append(prefix + "match address %s" % access_list) + commands.append(prefix + "set peer " + siteconn['peer_address']) + commands.append(prefix + "set pfs " + ipsecpolicy['pfs']) + commands.append((prefix + + "set security-association lifetime seconds " + + str(ipsecpolicy['lifetime']['value']))) + commands.append(prefix + "set ikev1 transform-set " + tfset_name) + commands.append("crypto map %s interface %s" % ( + name, + self.external_intf_name)) + return commands + + def _configure_tunnel_group(self, fip, ipsec_conn, delete=False): + commands = [] + if delete: + commands = ["clear conf tunnel-group %s" % ( + ipsec_conn['peer_address'])] + return commands + commands.append("tunnel-group %s type ipsec-l2l" % ( + ipsec_conn['peer_address'])) + commands.append("tunnel-group %s ipsec-attributes" % ( + ipsec_conn['peer_address'])) + commands.append("ikev1 pre-shared-key %s" % ipsec_conn['psk']) + return commands + + def _configure_bulk_cli(self, mgmt_ip, commands): + resource_uri = "/api/cli" + url = const.REQUEST_URL % (mgmt_ip, resource_uri) + commands.append("write memory") + data = {"commands": commands} + msg = "sending commands = %s" % commands + LOG.debug(msg) + self.rest_api.post(url, data, self.auth) + + def _get_unique_sequenceno(self, mgmt_ip): + uri = "/api/vpn/cryptomaps/%s/entries" % self.external_intf_name + url = const.REQUEST_URL % (mgmt_ip, uri) + resp = self.rest_api.get(url, self.auth) + if resp.status_code == 404: + resp = {} + used_seq = [] + if resp.get('items'): + used_seq = [item['sequence'] for item in resp['items']] + if not used_seq: + return 1 + for i in xrange(1, max(used_seq) + 2): + if i not in used_seq: + return i + + def get_delete_seqno(self, mgmt_ip, peer, peer_cidr=None): + uri = "/api/vpn/cryptomaps/%s/entries" % self.external_intf_name + url = const.REQUEST_URL % (mgmt_ip, uri) + resp = self.rest_api.get(url, self.auth) + if resp.status_code == 404 or not resp: + return 1 + resp = resp.json() + if resp.get('items'): + for item in resp.get('items'): + # if (peer in item['peer'] and + # item['matchingTrafficSelector']['objectId'] == ( + # (peer_cidr.replace('/', '_')): + if peer in item['peer']: + return item['sequence'] diff --git a/gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_constants.py b/gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_constants.py new file mode 100644 index 0000000000..aa8c157a74 --- /dev/null +++ b/gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_constants.py @@ -0,0 +1,18 @@ +# 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. + + +SERVICE_VENDOR = 'vyos' +CONFIGURATION_SERVER_PORT = 8888 +request_url = "http://%s:%s/%s" + +REST_TIMEOUT = 90 diff --git a/gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_driver.py b/gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_driver.py index 5a28cf0801..713516f518 100644 --- a/gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_driver.py +++ b/gbpservice/nfp/configurator/drivers/vpn/vyos/vyos_vpn_driver.py @@ -15,7 +15,10 @@ import requests from gbpservice.nfp.configurator.drivers.base import base_driver -from gbpservice.nfp.configurator.lib import vpn_constants as const +from gbpservice.nfp.configurator.drivers.vpn.vyos import ( + vyos_vpn_constants as const) +from gbpservice.nfp.configurator.lib import constants as common_const +from gbpservice.nfp.configurator.lib import vpn_constants as vpn_const from gbpservice.nfp.core import log as nfp_logging from oslo_concurrency import lockutils @@ -234,7 +237,8 @@ def _error_state(self, context, vpnsvc, message=''): Returns: None """ self.agent.update_status( - context, self._update_service_status(vpnsvc, const.STATE_ERROR)) + context, self._update_service_status(vpnsvc, + vpn_const.STATE_ERROR)) raise ResourceErrorState(name='vpn_service', id=vpnsvc['id'], message=message) @@ -249,7 +253,8 @@ def _active_state(self, context, vpnsvc): Returns: None """ self.agent.update_status( - context, self._update_service_status(vpnsvc, const.STATE_ACTIVE)) + context, self._update_service_status(vpnsvc, + vpn_const.STATE_ACTIVE)) def _get_local_cidr(self, vpn_svc): svc_desc = vpn_svc['description'] @@ -290,14 +295,13 @@ def validate(self, context, vpnsvc): self._active_state(context, vpnsvc) -class VpnGenericConfigDriver(object): +class VpnGenericConfigDriver(base_driver.BaseDriver): """ VPN generic config driver for handling device configurations requests. This driver class implements VPN configuration. """ - def __init__(self, conf): - self.conf = conf + def __init__(self): self.timeout = const.REST_TIMEOUT def _configure_static_ips(self, resource_data): @@ -365,7 +369,7 @@ def _configure_static_ips(self, resource_data): msg = ("Static IPs successfully added.") LOG.info(msg) - return const.STATUS_SUCCESS + return common_const.STATUS_SUCCESS def configure_interfaces(self, context, resource_data): """ Configure interfaces for the service VM. @@ -381,6 +385,28 @@ def configure_interfaces(self, context, resource_data): Returns: SUCCESS/Failure message with reason. """ + mgmt_ip = resource_data['mgmt_ip'] + + try: + result_log_forward = self._configure_log_forwarding( + const.request_url, mgmt_ip, self.port) + except Exception as err: + msg = ("Failed to configure log forwarding for service at %s. " + "Error: %s" % (mgmt_ip, err)) + LOG.error(msg) + return msg + else: + if result_log_forward == common_const.UNHANDLED: + pass + elif result_log_forward != common_const.STATUS_SUCCESS: + msg = ("Failed to configure log forwarding for service at %s. " + "Error: %s" % (mgmt_ip, err)) + LOG.error(msg) + return result_log_forward + else: + msg = ("Configured log forwarding for service at %s. " + "Result: %s" % (mgmt_ip, result_log_forward)) + LOG.info(msg) try: result_static_ips = self._configure_static_ips(resource_data) @@ -389,7 +415,7 @@ def configure_interfaces(self, context, resource_data): LOG.error(msg) return msg else: - if result_static_ips != const.STATUS_SUCCESS: + if result_static_ips != common_const.STATUS_SUCCESS: return result_static_ips else: msg = ("Added static IPs. Result: %s" % result_static_ips) @@ -399,8 +425,6 @@ def configure_interfaces(self, context, resource_data): provider_mac=resource_data['provider_mac'], stitching_mac=resource_data['stitching_mac']) - mgmt_ip = resource_data['mgmt_ip'] - url = const.request_url % (mgmt_ip, const.CONFIGURATION_SERVER_PORT, 'add_rule') data = jsonutils.dumps(rule_info) @@ -436,7 +460,7 @@ def configure_interfaces(self, context, resource_data): msg = ("Persistent rule successfully added.") LOG.info(msg) - return const.STATUS_SUCCESS + return common_const.STATUS_SUCCESS def _clear_static_ips(self, resource_data): """ Clear static IPs for provider and stitching @@ -498,7 +522,7 @@ def _clear_static_ips(self, resource_data): msg = ("Static IPs successfully removed.") LOG.info(msg) - return const.STATUS_SUCCESS + return common_const.STATUS_SUCCESS def clear_interfaces(self, context, resource_data): """ Clear interfaces for the service VM. @@ -522,7 +546,7 @@ def clear_interfaces(self, context, resource_data): LOG.error(msg) return msg else: - if result_static_ips != const.STATUS_SUCCESS: + if result_static_ips != common_const.STATUS_SUCCESS: return result_static_ips else: msg = ("Successfully removed static IPs. " @@ -570,7 +594,7 @@ def clear_interfaces(self, context, resource_data): raise Exception(msg) msg = ("Persistent rule successfully deleted.") LOG.info(msg) - return const.STATUS_SUCCESS + return common_const.STATUS_SUCCESS def configure_routes(self, context, resource_data): """ Configure routes for the service VM. @@ -632,7 +656,7 @@ def configure_routes(self, context, resource_data): LOG.error(msg) return msg - if resp.status_code in const.SUCCESS_CODES: + if resp.status_code in common_const.SUCCESS_CODES: message = jsonutils.loads(resp.text) if message.get("status", False): msg = ("Route configured successfully for VYOS" @@ -650,7 +674,7 @@ def configure_routes(self, context, resource_data): % (active_configured)) LOG.info(msg) if active_configured: - return const.STATUS_SUCCESS + return common_const.STATUS_SUCCESS else: return ("Failed to configure source route. Response code: %s." "Response Content: %r" % (resp.status_code, resp.content)) @@ -712,30 +736,31 @@ def clear_routes(self, context, resource_data): LOG.error(msg) return msg - if resp.status_code in const.SUCCESS_CODES: + if resp.status_code in common_const.SUCCESS_CODES: active_configured = True msg = ("Route deletion status : %r " % (active_configured)) LOG.info(msg) if active_configured: - return const.STATUS_SUCCESS + return common_const.STATUS_SUCCESS else: return ("Failed to delete source route. Response code: %s." "Response Content: %r" % (resp.status_code, resp.content)) -class VpnaasIpsecDriver(VpnGenericConfigDriver, base_driver.BaseDriver): +class VpnaasIpsecDriver(VpnGenericConfigDriver): """ Driver class for implementing VPN IPSEC configuration requests from VPNaas Plugin. """ - service_type = const.SERVICE_TYPE + service_type = vpn_const.SERVICE_TYPE service_vendor = const.SERVICE_VENDOR def __init__(self, conf): self.conf = conf + self.port = const.CONFIGURATION_SERVER_PORT self.handlers = { 'vpn_service': { 'create': self.create_vpn_service}, @@ -743,7 +768,7 @@ def __init__(self, conf): 'create': self.create_ipsec_conn, 'update': self.update_ipsec_conn, 'delete': self.delete_ipsec_conn}} - super(VpnaasIpsecDriver, self).__init__(conf) + super(VpnaasIpsecDriver, self).__init__() def _update_conn_status(self, conn, status): """ @@ -781,7 +806,7 @@ def _error_state(self, context, conn, message=''): self.agent.update_status( context, self._update_conn_status(conn, - const.STATE_ERROR)) + vpn_const.STATE_ERROR)) raise ResourceErrorState(id=conn['id'], message=message) def _init_state(self, context, conn): @@ -798,11 +823,11 @@ def _init_state(self, context, conn): LOG.info(msg) self.agent.update_status( context, self._update_conn_status(conn, - const.STATE_INIT)) + vpn_const.STATE_INIT)) for item in context['service_info']['ipsec_site_conns']: if item['id'] == conn['id']: - item['status'] = const.STATE_INIT + item['status'] = vpn_const.STATE_INIT def _get_fip_from_vpnsvc(self, vpn_svc): svc_desc = vpn_svc['description'] @@ -982,7 +1007,7 @@ def _ipsec_get_tenant_conns(self, context, mgmt_fip, conn, copy_conns = copy.deepcopy(conn_list) for tconn in copy_conns: if tconn['status'] == ( - const.STATE_PENDING and tconn in conn_list): + vpn_const.STATE_PENDING and tconn in conn_list): conn_list.remove(tconn) return conn_list @@ -1068,7 +1093,7 @@ def _ipsec_is_state_changed(self, svc_context, conn, fip): c_state = None lcidr = self.\ _get_ipsec_tunnel_local_cidr(svc_context) - if conn['status'] == const.STATE_INIT: + if conn['status'] == vpn_const.STATE_INIT: tunnel = { 'peer_address': conn['peer_address'], 'local_cidr': lcidr, @@ -1079,11 +1104,11 @@ def _ipsec_is_state_changed(self, svc_context, conn, fip): state = output['state'] if state.upper() == 'UP' and\ - conn['status'] != const.STATE_ACTIVE: - c_state = const.STATE_ACTIVE + conn['status'] != vpn_const.STATE_ACTIVE: + c_state = vpn_const.STATE_ACTIVE if state.upper() == 'DOWN' and\ - conn['status'] == const.STATE_ACTIVE: - c_state = const.STATE_PENDING + conn['status'] == vpn_const.STATE_ACTIVE: + c_state = vpn_const.STATE_PENDING if c_state: return c_state, True @@ -1268,19 +1293,3 @@ def _vpnservice_updated(context, resource_data): self.handlers[rsrc][reason](context, resource_data) return _vpnservice_updated(context, resource_data) - - def configure_healthmonitor(self, context, resource_data): - """Overriding BaseDriver's configure_healthmonitor(). - It does netcat to CONFIGURATION_SERVER_PORT 8888. - Configuration agent runs inside service vm.Once agent is up and - reachable, service vm is assumed to be active. - :param context - context - :param resource_data - resource_data coming from orchestrator - - Returns: SUCCESS/FAILED - - """ - ip = resource_data.get('mgmt_ip') - port = str(const.CONFIGURATION_SERVER_PORT) - command = 'nc ' + ip + ' ' + port + ' -z' - return self._check_vm_health(command) diff --git a/gbpservice/nfp/configurator/lib/data_filter.py b/gbpservice/nfp/configurator/lib/data_filter.py index 2adb4f9ede..e9c8a6e539 100644 --- a/gbpservice/nfp/configurator/lib/data_filter.py +++ b/gbpservice/nfp/configurator/lib/data_filter.py @@ -36,6 +36,7 @@ def call(self, context, msg): """ try: + filters = {} for fk, fv in msg['args'].items(): if dict == type(fv): filters = fv diff --git a/gbpservice/nfp/configurator/lib/generic_config_constants.py b/gbpservice/nfp/configurator/lib/generic_config_constants.py index 8018a7a10f..6ae11e4303 100644 --- a/gbpservice/nfp/configurator/lib/generic_config_constants.py +++ b/gbpservice/nfp/configurator/lib/generic_config_constants.py @@ -22,4 +22,4 @@ MAX_FAIL_COUNT = 12 # 5 secs delay * 12 = 60 secs INITIAL = 'initial' FOREVER = 'forever' -INITIAL_HM_RETRIES = 24 # 5 secs delay * 24 = 120 secs +INITIAL_HM_RETRIES = 30 # 5 secs delay * 30 = 150 secs diff --git a/gbpservice/nfp/configurator/lib/vpn_constants.py b/gbpservice/nfp/configurator/lib/vpn_constants.py index f248469cb2..3998e3aad4 100644 --- a/gbpservice/nfp/configurator/lib/vpn_constants.py +++ b/gbpservice/nfp/configurator/lib/vpn_constants.py @@ -13,32 +13,15 @@ DRIVERS_DIR = 'gbpservice.nfp.configurator.drivers.vpn' SERVICE_TYPE = 'vpn' -SERVICE_VENDOR = 'vyos' + STATE_PENDING = 'PENDING_CREATE' STATE_INIT = 'INIT' STATE_ACTIVE = 'ACTIVE' STATE_ERROR = 'ERROR' -NEUTRON = 'NEUTRON' - -STATUS_ACTIVE = "ACTIVE" -STATUS_DELETED = "DELETED" -STATUS_UPDATED = "UPDATED" -STATUS_ERROR = "ERROR" -STATUS_SUCCESS = "SUCCESS" -CONFIGURATION_SERVER_PORT = 8888 -REST_TIMEOUT = 90 -request_url = "http://%s:%s/%s" -SUCCESS_CODES = [200, 201, 202, 203, 204] -ERROR_CODES = [400, 404, 500] -VYOS = 'vyos' -SM_RPC_TOPIC = 'VPN-sm-topic' -VPN_RPC_TOPIC = "vpn_topic" VPN_GENERIC_CONFIG_RPC_TOPIC = "vyos_vpn_topic" VPN_PLUGIN_TOPIC = 'vpn_plugin' VPN_AGENT_TOPIC = 'vpn_agent' - -CONFIGURATION_SERVER_PORT = '8888' diff --git a/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py b/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py index cbcea9b3a9..4716d74616 100644 --- a/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py +++ b/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py @@ -942,6 +942,9 @@ def plug_network_function_device_interfaces(self, device_data, token = device_data['token'] tenant_id = device_data['tenant_id'] + image_name = self._get_image_name(device_data) + if image_name: + self._update_vendor_data(device_data) update_ifaces = [] try: diff --git a/gbpservice/nfp/orchestrator/modules/device_orchestrator.py b/gbpservice/nfp/orchestrator/modules/device_orchestrator.py index cde7a78fe4..6b82bc67ab 100644 --- a/gbpservice/nfp/orchestrator/modules/device_orchestrator.py +++ b/gbpservice/nfp/orchestrator/modules/device_orchestrator.py @@ -557,6 +557,22 @@ def _update_device_data(self, device, device_data): device.update(device_data) return device + def _make_ports_dict(self, consumer, provider, port_type): + t_ports = [] + if consumer[port_type]: + t_ports.append({ + 'id': consumer[port_type]['id'], + 'port_classification': consumer['port_classification'], + 'port_model': consumer['port_model']}) + + if provider[port_type]: + t_ports.append({ + 'id': provider[port_type]['id'], + 'port_classification': provider['port_classification'], + 'port_model': provider['port_model']}) + + return t_ports + def _prepare_device_data_from_nfp_context(self, nfp_context): device_data = {} @@ -581,17 +597,10 @@ def _prepare_device_data_from_nfp_context(self, nfp_context): provider = nfp_context['provider'] ports = [] - if consumer['port']: - ports.append({ - 'id': consumer['port']['id'], - 'port_classification': consumer['port_classification'], - 'port_model': consumer['port_model']}) - - if provider['port']: - ports.append({ - 'id': provider['port']['id'], - 'port_classification': provider['port_classification'], - 'port_model': provider['port_model']}) + if consumer['port_model'] == 'gbp_policy_target': + ports = self._make_ports_dict(consumer, provider, 'pt') + else: + ports = self._make_ports_dict(consumer, provider, 'port') device_data['management_network_info'] = management_network_info diff --git a/gbpservice/nfp/service_plugins/vpn/drivers/nfp_vpnaas_driver.py b/gbpservice/nfp/service_plugins/vpn/drivers/nfp_vpnaas_driver.py index 0a1211d7cc..340169c339 100644 --- a/gbpservice/nfp/service_plugins/vpn/drivers/nfp_vpnaas_driver.py +++ b/gbpservice/nfp/service_plugins/vpn/drivers/nfp_vpnaas_driver.py @@ -14,13 +14,15 @@ import time from gbpservice.nfp.config_orchestrator.common import topics -from neutron_lib import exceptions + from neutron.common import rpc as n_rpc from neutron.db import agents_db from neutron.db import agentschedulers_db + from neutron import manager -from neutron_vpnaas.services.vpn.plugin import VPNPlugin +from neutron_lib import exceptions from neutron_vpnaas.services.vpn.plugin import VPNDriverPlugin +from neutron_vpnaas.services.vpn.plugin import VPNPlugin from neutron_vpnaas.services.vpn.service_drivers import base_ipsec from oslo_log import log as logging @@ -93,7 +95,7 @@ def _is_agent_hosting_vpnservice(self, agent): def _get_agent_hosting_vpnservice(self, admin_context, vpnservice_id): filters = {'agent_type': [AGENT_TYPE_VPN]} agents = manager.NeutronManager.get_plugin().get_agents( - admin_context, filters=filters) + admin_context, filters=filters) try: for agent in agents: @@ -109,10 +111,11 @@ def _get_agent_hosting_vpnservice(self, admin_context, vpnservice_id): if not agent['alive']: continue return agent - except: + except Exception: raise VPNAgentNotFound() - LOG.error(_('No active vpn agent found. Configuration will fail.')) + msg = 'No active vpn agent found. Configuration will fail.' + LOG.error(msg) raise VPNAgentHostingServiceNotFound(vpnservice_id=vpnservice_id) def _agent_notification(self, context, method, vpnservice_id, @@ -124,10 +127,11 @@ def _agent_notification(self, context, method, vpnservice_id, vpn_agent = self._get_agent_hosting_vpnservice( admin_context, vpnservice_id) - LOG.debug(_('Notify agent at %(topic)s.%(host)s the message ' - '%(method)s %(args)s'), { + msg = ('Notify agent at %(topic)s.%(host)s the message ' + '%(method)s %(args)s'), { 'topic': self.topic, 'host': vpn_agent['host'], - 'method': method, 'args': kwargs}) + 'method': method, 'args': kwargs} + LOG.debug(msg) cctxt = self.client.prepare(server=vpn_agent['host'], version=version) @@ -141,8 +145,9 @@ def vpnservice_updated(self, context, vpnservice_id, **kwargs): self._agent_notification( context, 'vpnservice_updated', vpnservice_id, **kwargs) - except: - LOG.error(_('Notifying agent failed')) + except Exception: + msg = 'Notifying agent failed' + LOG.error(msg) class NFPIPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver): diff --git a/test-requirements.txt b/test-requirements.txt index 0abfc34831..db41102e9a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -18,6 +18,8 @@ cairocffi>=0.1 cliff>=1.15.0 # Apache-2.0 coverage>=3.6 # Apache-2.0 fixtures>=1.3.1 # Apache-2.0/BSD +ipaddr==2.1.10 +iptools==0.6.1 httplib2>=0.7.5 mock>=1.2 # BSD python-subunit>=0.0.18 # Apache-2.0/BSD From be5ec8c20ebd30c87cfdb61d292ad74b82acf712 Mon Sep 17 00:00:00 2001 From: Suresh Dharavath Date: Wed, 29 Jun 2016 12:18:28 +0530 Subject: [PATCH 2/2] perf heat driver and vpn verdor api changes --- .../config_drivers/heat_driver.py | 99 +++++++++++++++++++ .../vyos/oc_config_server/vpn_api_server.py | 2 +- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py b/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py index bef908fda0..d17dc3494b 100644 --- a/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py +++ b/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py @@ -876,6 +876,105 @@ def _create_node_config_data(self, auth_token, tenant_id, 'description'] = str(common_desc) nf_desc = str(firewall_desc) + elif service_type == pconst.VPN: + # rvpn_l3_policy = self._get_rvpn_l3_policy(auth_token, + # provider, update) + # if rvpn_l3_policy is None: + # return None, None + # config_param_values['ClientAddressPoolCidr'] = rvpn_l3_policy[ + # 'ip_pool'] + config_param_values['Subnet'] = ( + consumer_port['fixed_ips'][0]['subnet_id'] + if consumer_port else None) + l2p = self.gbp_client.get_l2_policy( + auth_token, provider['l2_policy_id']) + l3p = self.gbp_client.get_l3_policy( + auth_token, l2p['l3_policy_id']) + config_param_values['RouterId'] = l3p['routers'][0] + stitching_cidr = service_details['consumer_subnet']['cidr'] + mgmt_gw_ip = self._get_management_gw_ip(auth_token) + if not mgmt_gw_ip: + return None, None + + services_nsp = self.gbp_client.get_network_service_policies( + auth_token, + filters={'name': ['nfp_services_nsp']}) + if not services_nsp: + fip_nsp = { + 'network_service_policy': { + 'name': 'nfp_services_nsp', + 'description': 'nfp_implicit_resource', + 'shared': False, + 'tenant_id': tenant_id, + 'network_service_params': [ + {"type": "ip_pool", "value": "nat_pool", + "name": "vpn_svc_external_access"}] + } + } + nsp = self.gbp_client.create_network_service_policy( + auth_token, fip_nsp) + else: + nsp = services_nsp[0] + if not base_mode_support: + stitching_pts = self.gbp_client.get_policy_targets( + auth_token, + filters={'port_id': [consumer_port['id']]}) + if not stitching_pts: + LOG.error(_LE("Policy target is not created for the " + "stitching port")) + return None, None + stitching_ptg_id = ( + stitching_pts[0]['policy_target_group_id']) + else: + stitching_ptg_id = consumer['id'] + self.gbp_client.update_policy_target_group( + auth_token, stitching_ptg_id, + {'policy_target_group': { + 'network_service_policy_id': nsp['id']}}) + if not base_mode_support: + floatingips = self.neutron_client.get_floating_ips( + auth_token, consumer_port['id']) + if not floatingips: + LOG.error(_LE("Floating IP for VPN Service has been " + "disassociated Manually")) + return None, None + for fip in floatingips: + if consumer_port['fixed_ips'][0]['ip_address'] == fip[ + 'fixed_ip_address']: + stitching_port_fip = fip['floating_ip_address'] + try: + desc = ('fip=' + mgmt_ip + + ";tunnel_local_cidr=" + + provider_cidr + ";user_access_ip=" + + stitching_port_fip + ";fixed_ip=" + + consumer_port['fixed_ips'][0]['ip_address'] + + ';service_vendor=' + service_vendor + + ';stitching_cidr=' + stitching_cidr + + ';stitching_gateway=' + service_details[ + 'consumer_subnet']['gateway_ip'] + + ';mgmt_gw_ip=' + mgmt_gw_ip + + ';network_function_id=' + network_function['id']) + except Exception: + LOG.error(_LE("Problem in preparing description, some of " + "the fields might not have initialized")) + return None, None + stack_params['ServiceDescription'] = desc + siteconn_keys = self._get_site_conn_keys( + stack_template[resources_key], + is_template_aws_version, + 'OS::Neutron::IPsecSiteConnection') + for siteconn_key in siteconn_keys: + stack_template[resources_key][siteconn_key][ + properties_key]['description'] = str(common_desc) + + vpnservice_key = self._get_heat_resource_key( + stack_template[resources_key], + is_template_aws_version, + 'OS::Neutron::VPNService') + stack_template[resources_key][vpnservice_key][properties_key][ + 'description'] = str(common_desc) + + nf_desc = str(desc) if nf_desc: network_function['description'] = network_function[ diff --git a/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/vpn_api_server.py b/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/vpn_api_server.py index e4f7c28c18..7be0de0264 100644 --- a/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/vpn_api_server.py +++ b/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/vpn_api_server.py @@ -87,7 +87,7 @@ class NoInterfaceOnCidr(Exception): def __init__(self, **kwargs): - self.message = _("No interface in the network '%(cidr)s'") % kwargs + self.message = ("No interface in the network '%(cidr)s'") % kwargs class VPNHandler(configOpts):