def __init__(self, vpn_service, host):
     self.conf = vpn_service.conf
     self.host = host
     self.conn = n_rpc.create_connection(new=True)
     self.context = context.get_admin_context_without_session()
     self.topic = topics.NUAGE_IPSEC_AGENT_TOPIC
     self.processes = {}
     self.routers = {}
     self.process_status_cache = {}
     self.endpoints = [self]
     self.conn.create_consumer(self.topic, self.endpoints)
     self.conn.consume_in_threads()
     self.agent_rpc = NuageIPsecVpnDriverApi(
         topics.NUAGE_IPSEC_DRIVER_TOPIC)
     self.process_status_cache_check = loopingcall.FixedIntervalLoopingCall(
         self.report_status, self.context)
     self.process_status_cache_check.start(interval=20)
     self.nuage_if_driver = NuageInterfaceDriver(cfg.CONF)
 def __init__(self, vpn_service, host):
     self.conf = vpn_service.conf
     self.host = host
     self.conn = n_rpc.create_connection(new=True)
     self.context = context.get_admin_context_without_session()
     self.topic = topics.NUAGE_IPSEC_AGENT_TOPIC
     self.processes = {}
     self.routers = {}
     self.process_status_cache = {}
     self.endpoints = [self]
     self.conn.create_consumer(self.topic, self.endpoints)
     self.conn.consume_in_threads()
     self.agent_rpc = NuageIPsecVpnDriverApi(topics.NUAGE_IPSEC_DRIVER_TOPIC)
     self.process_status_cache_check = loopingcall.FixedIntervalLoopingCall(self.report_status, self.context)
     self.process_status_cache_check.start(interval=20)
     self.nuage_if_driver = NuageInterfaceDriver(cfg.CONF)
class NuageIPsecDriver(device_drivers.DeviceDriver):
    def __init__(self, vpn_service, host):
        self.conf = vpn_service.conf
        self.host = host
        self.conn = n_rpc.create_connection(new=True)
        self.context = context.get_admin_context_without_session()
        self.topic = topics.NUAGE_IPSEC_AGENT_TOPIC
        self.processes = {}
        self.routers = {}
        self.process_status_cache = {}
        self.endpoints = [self]
        self.conn.create_consumer(self.topic, self.endpoints)
        self.conn.consume_in_threads()
        self.agent_rpc = NuageIPsecVpnDriverApi(
            topics.NUAGE_IPSEC_DRIVER_TOPIC)
        self.process_status_cache_check = loopingcall.FixedIntervalLoopingCall(
            self.report_status, self.context)
        self.process_status_cache_check.start(interval=20)
        self.nuage_if_driver = NuageInterfaceDriver(cfg.CONF)

    def _get_l3_plugin(self):
        return manager.NeutronManager.get_service_plugins().get(
            constants.L3_ROUTER_NAT)

    def get_namespace(self, router_id):
        """Get namespace of router.

        :router_id: router_id
        :returns: namespace string.
        """
        return 'vpn-' + router_id

    def vpnservice_updated(self, context, **kwargs):
        """Vpnservice updated rpc handler

        VPN Service Driver will call this method
        when vpnservices updated.
        Then this method start sync with server.
        """
        router = kwargs.get('router', None)
        self.sync(context, [router] if router else [])

    def tracking(self, context, **kwargs):
        """Handling create router event.

        Agent calls this method, when the process namespace is ready.
        Note: process_id == router_id == vpnservice_id
        """
        router = kwargs.get('router', None)
        process_id = router['id']
        self.routers[process_id] = process_id
        if process_id in self.processes:
            # In case of vpnservice is created
            # before vpn service namespace
            process = self.processes[process_id]
            process.enable()

    def non_tracking(self, context, **kwargs):
        router = kwargs.get('router', None)
        process_id = router['id']
        self.destroy_process(process_id)
        if process_id in self.routers:
            del self.routers[process_id]

    def ensure_process(self, process_id, vpnservice=None):
        """Ensuring process.

        If the process doesn't exist, it will create process
        and store it in self.processs
        """
        process = self.processes.get(process_id)
        if not process or not process.namespace:
            namespace = self.get_namespace(process_id)
            process = self.create_process(process_id, vpnservice, namespace)
            self.processes[process_id] = process
        elif vpnservice:
            process.update_vpnservice(vpnservice)
        return process

    @lockutils.synchronized('vpn-agent', 'neutron-')
    def sync(self, context, routers):
        """Sync status with server side.

        :param context: context object for RPC call
        :param routers: Router objects which is created in this sync event

        There could be many failure cases should be
        considered including the followings.
        1) Agent class restarted
        2) Failure on process creation
        3) VpnService is deleted during agent down
        4) RPC failure

        In order to handle, these failure cases,
        the driver needs to take sync strategies.

        """
        vpnservices = self.agent_rpc.get_vpn_services_on_host(
            context, self.host)
        router_ids = [vpnservice['router_id'] for vpnservice in vpnservices]
        sync_router_ids = [router['id'] for router in routers]
        self._sync_vpn_processes(vpnservices, sync_router_ids)
        self._delete_vpn_processes(sync_router_ids, router_ids)
        self._cleanup_stale_vpn_processes(router_ids)
        self.report_status(context)

    def get_process_status_cache(self, process):
        if not self.process_status_cache.get(process.id):
            self.process_status_cache[process.id] = {
                'status': None,
                'id': process.vpnservice['id'],
                'updated_pending_status': False,
                'ipsec_site_connections': {}
            }
        return self.process_status_cache[process.id]

    def report_status(self, context):
        status_changed_vpn_services = []
        for process in self.processes.values():
            previous_status = self.get_process_status_cache(process)
            if self.is_status_updated(process, previous_status):
                new_status = self.copy_process_status(process)
                self.update_downed_connections(process.id, new_status)
                status_changed_vpn_services.append(new_status)
                self.process_status_cache[process.id] = (
                    self.copy_process_status(process))
                # We need unset updated_pending status after it
                # is reported to the server side
                self.unset_updated_pending_status(process)

        if status_changed_vpn_services:
            self.agent_rpc.update_status(context, status_changed_vpn_services)

    def _sync_vpn_processes(self, vpnservices, sync_router_ids):
        for vpnservice in vpnservices:
            if vpnservice['router_id'] not in self.processes or (
                    vpnservice['router_id'] in sync_router_ids):
                process = self.ensure_process(vpnservice['router_id'],
                                              vpnservice=vpnservice)
                router = self.routers.get(vpnservice['router_id'])
                if not router:
                    continue
                process.update()

    def _delete_vpn_processes(self, sync_router_ids, vpn_router_ids):
        for process_id in sync_router_ids:
            if process_id not in vpn_router_ids:
                self.destroy_process(process_id)

    def _cleanup_stale_vpn_processes(self, vpn_router_ids):
        process_ids = [
            pid for pid in self.processes if pid not in vpn_router_ids
        ]
        for process_id in process_ids:
            self.destroy_process(process_id)

    def is_status_updated(self, process, previous_status):
        if process.updated_pending_status:
            return True
        if process.status != previous_status['status']:
            return True
        if (process.connection_status !=
                previous_status['ipsec_site_connections']):
            return True

    def unset_updated_pending_status(self, process):
        process.updated_pending_status = False
        for connection_status in process.connection_status.values():
            connection_status['updated_pending_status'] = False

    def copy_process_status(self, process):
        return {
            'id': process.vpnservice['id'],
            'status': process.status,
            'updated_pending_status': process.updated_pending_status,
            'ipsec_site_connections': copy.deepcopy(process.connection_status)
        }

    def update_downed_connections(self, process_id, new_status):
        """Update info to be reported, if connections just went down.

            If there is no longer any information for a connection, because it
            has been removed (e.g. due to an admin down of VPN service or IPSec
            connection), but there was previous status information for the
            connection, mark the connection as down for reporting purposes.
        """
        if process_id in self.process_status_cache:
            for conn in self.process_status_cache[process_id][IPSEC_CONNS]:
                if conn not in new_status[IPSEC_CONNS]:
                    new_status[IPSEC_CONNS][conn] = {
                        'status': constants.DOWN,
                        'updated_pending_status': True
                    }

    def create_router(self, router):
        """Handling create router event."""
        pass

    def destroy_router(self, process_id):
        pass

    def destroy_process(self, process_id):
        """Destroy process.

        Disable the process and remove the process
        manager for the processes that no longer are running vpn service.
        """
        if process_id in self.processes:
            process = self.processes[process_id]
            process.disable()
            if process_id in self.processes:
                del self.processes[process_id]

    def plug_to_ovs(self, context, **kwargs):
        self.nuage_if_driver.plug(kwargs['network_id'], kwargs['port_id'],
                                  kwargs['device_name'], kwargs['mac'],
                                  'alubr0', kwargs['ns_name'])

        self.nuage_if_driver.init_l3(kwargs['device_name'], kwargs['cidr'],
                                     kwargs['ns_name'])
        device = ip_lib.IPDevice(kwargs['device_name'],
                                 namespace=kwargs['ns_name'])
        for gateway_ip in kwargs['gw_ip']:
            device.route.add_gateway(gateway_ip)

    def unplug_from_ovs(self, context, **kwargs):
        self.nuage_if_driver.unplug(kwargs['device_name'], 'alubr0',
                                    kwargs['ns_name'])
        ip = ip_lib.IPWrapper(kwargs['ns_name'])
        ip.garbage_collect_namespace()
        # On Redhat deployments an additional directory is created named
        # 'ip_vti0' in the namespace which prevents the cleanup
        # of namespace by the neutron agent in 'ip_lib.py' which we clean.
        if kwargs['ns_name'] in ip.get_namespaces():
            ip.netns.delete(kwargs['ns_name'])
class NuageIPsecDriver(device_drivers.DeviceDriver):
    def __init__(self, vpn_service, host):
        self.conf = vpn_service.conf
        self.host = host
        self.conn = n_rpc.create_connection(new=True)
        self.context = context.get_admin_context_without_session()
        self.topic = topics.NUAGE_IPSEC_AGENT_TOPIC
        self.processes = {}
        self.routers = {}
        self.process_status_cache = {}
        self.endpoints = [self]
        self.conn.create_consumer(self.topic, self.endpoints)
        self.conn.consume_in_threads()
        self.agent_rpc = NuageIPsecVpnDriverApi(topics.NUAGE_IPSEC_DRIVER_TOPIC)
        self.process_status_cache_check = loopingcall.FixedIntervalLoopingCall(self.report_status, self.context)
        self.process_status_cache_check.start(interval=20)
        self.nuage_if_driver = NuageInterfaceDriver(cfg.CONF)

    def _get_l3_plugin(self):
        return manager.NeutronManager.get_service_plugins().get(constants.L3_ROUTER_NAT)

    def get_namespace(self, router_id):
        """Get namespace of router.

        :router_id: router_id
        :returns: namespace string.
        """
        return "vpn-" + router_id

    def vpnservice_updated(self, context, **kwargs):
        """Vpnservice updated rpc handler

        VPN Service Driver will call this method
        when vpnservices updated.
        Then this method start sync with server.
        """
        router = kwargs.get("router", None)
        self.sync(context, [router] if router else [])

    def tracking(self, context, **kwargs):
        """Handling create router event.

        Agent calls this method, when the process namespace is ready.
        Note: process_id == router_id == vpnservice_id
        """
        router = kwargs.get("router", None)
        process_id = router["id"]
        self.routers[process_id] = process_id
        if process_id in self.processes:
            # In case of vpnservice is created
            # before vpn service namespace
            process = self.processes[process_id]
            process.enable()

    def non_tracking(self, context, **kwargs):
        router = kwargs.get("router", None)
        process_id = router["id"]
        self.destroy_process(process_id)
        if process_id in self.routers:
            del self.routers[process_id]

    def ensure_process(self, process_id, vpnservice=None):
        """Ensuring process.

        If the process doesn't exist, it will create process
        and store it in self.processs
        """
        process = self.processes.get(process_id)
        if not process or not process.namespace:
            namespace = self.get_namespace(process_id)
            process = self.create_process(process_id, vpnservice, namespace)
            self.processes[process_id] = process
        elif vpnservice:
            process.update_vpnservice(vpnservice)
        return process

    @lockutils.synchronized("vpn-agent", "neutron-")
    def sync(self, context, routers):
        """Sync status with server side.

        :param context: context object for RPC call
        :param routers: Router objects which is created in this sync event

        There could be many failure cases should be
        considered including the followings.
        1) Agent class restarted
        2) Failure on process creation
        3) VpnService is deleted during agent down
        4) RPC failure

        In order to handle, these failure cases,
        the driver needs to take sync strategies.

        """
        vpnservices = self.agent_rpc.get_vpn_services_on_host(context, self.host)
        router_ids = [vpnservice["router_id"] for vpnservice in vpnservices]
        sync_router_ids = [router["id"] for router in routers]
        self._sync_vpn_processes(vpnservices, sync_router_ids)
        self._delete_vpn_processes(sync_router_ids, router_ids)
        self._cleanup_stale_vpn_processes(router_ids)
        self.report_status(context)

    def get_process_status_cache(self, process):
        if not self.process_status_cache.get(process.id):
            self.process_status_cache[process.id] = {
                "status": None,
                "id": process.vpnservice["id"],
                "updated_pending_status": False,
                "ipsec_site_connections": {},
            }
        return self.process_status_cache[process.id]

    def report_status(self, context):
        status_changed_vpn_services = []
        for process in self.processes.values():
            previous_status = self.get_process_status_cache(process)
            if self.is_status_updated(process, previous_status):
                new_status = self.copy_process_status(process)
                self.update_downed_connections(process.id, new_status)
                status_changed_vpn_services.append(new_status)
                self.process_status_cache[process.id] = self.copy_process_status(process)
                # We need unset updated_pending status after it
                # is reported to the server side
                self.unset_updated_pending_status(process)

        if status_changed_vpn_services:
            self.agent_rpc.update_status(context, status_changed_vpn_services)

    def _sync_vpn_processes(self, vpnservices, sync_router_ids):
        for vpnservice in vpnservices:
            if vpnservice["router_id"] not in self.processes or (vpnservice["router_id"] in sync_router_ids):
                process = self.ensure_process(vpnservice["router_id"], vpnservice=vpnservice)
                router = self.routers.get(vpnservice["router_id"])
                if not router:
                    continue
                process.update()

    def _delete_vpn_processes(self, sync_router_ids, vpn_router_ids):
        for process_id in sync_router_ids:
            if process_id not in vpn_router_ids:
                self.destroy_process(process_id)

    def _cleanup_stale_vpn_processes(self, vpn_router_ids):
        process_ids = [pid for pid in self.processes if pid not in vpn_router_ids]
        for process_id in process_ids:
            self.destroy_process(process_id)

    def is_status_updated(self, process, previous_status):
        if process.updated_pending_status:
            return True
        if process.status != previous_status["status"]:
            return True
        if process.connection_status != previous_status["ipsec_site_connections"]:
            return True

    def unset_updated_pending_status(self, process):
        process.updated_pending_status = False
        for connection_status in process.connection_status.values():
            connection_status["updated_pending_status"] = False

    def copy_process_status(self, process):
        return {
            "id": process.vpnservice["id"],
            "status": process.status,
            "updated_pending_status": process.updated_pending_status,
            "ipsec_site_connections": copy.deepcopy(process.connection_status),
        }

    def update_downed_connections(self, process_id, new_status):
        """Update info to be reported, if connections just went down.

            If there is no longer any information for a connection, because it
            has been removed (e.g. due to an admin down of VPN service or IPSec
            connection), but there was previous status information for the
            connection, mark the connection as down for reporting purposes.
        """
        if process_id in self.process_status_cache:
            for conn in self.process_status_cache[process_id][IPSEC_CONNS]:
                if conn not in new_status[IPSEC_CONNS]:
                    new_status[IPSEC_CONNS][conn] = {"status": constants.DOWN, "updated_pending_status": True}

    def create_router(self, router):
        """Handling create router event."""
        pass

    def destroy_router(self, process_id):
        pass

    def destroy_process(self, process_id):
        """Destroy process.

        Disable the process and remove the process
        manager for the processes that no longer are running vpn service.
        """
        if process_id in self.processes:
            process = self.processes[process_id]
            process.disable()
            if process_id in self.processes:
                del self.processes[process_id]

    def plug_to_ovs(self, context, **kwargs):
        self.nuage_if_driver.plug(
            kwargs["network_id"], kwargs["port_id"], kwargs["device_name"], kwargs["mac"], "alubr0", kwargs["ns_name"]
        )

        self.nuage_if_driver.init_l3(kwargs["device_name"], kwargs["cidr"], kwargs["ns_name"])
        device = ip_lib.IPDevice(kwargs["device_name"], namespace=kwargs["ns_name"])
        for gateway_ip in kwargs["gw_ip"]:
            device.route.add_gateway(gateway_ip)

    def unplug_from_ovs(self, context, **kwargs):
        self.nuage_if_driver.unplug(kwargs["device_name"], "alubr0", kwargs["ns_name"])
        ip = ip_lib.IPWrapper(kwargs["ns_name"])
        ip.garbage_collect_namespace()
        # On Redhat deployments an additional directory is created named
        # 'ip_vti0' in the namespace which prevents the cleanup
        # of namespace by the neutron agent in 'ip_lib.py' which we clean.
        if kwargs["ns_name"] in ip.get_namespaces():
            ip.netns.delete(kwargs["ns_name"])