#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import collections
import copy
import functools
from unittest import mock

import netaddr
from neutron.agent.common import ovs_lib
from neutron.agent.l3 import l3_agent_extensions_manager as ext_manager
from neutron.agent.l3 import namespaces as n_namespaces
from neutron.agent.l3 import router_info
from neutron.agent import l3_agent as l3_agent_main
from neutron.agent.linux import external_process
from neutron.agent.linux import interface
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils as linux_utils
from neutron.common import config as common_config
from neutron.common import utils as common_utils
from neutron.conf.agent import common as agent_config
from neutron.conf import common as conf_common
from neutron.services.provider_configuration import serviceprovider_opts
from neutron.tests.common import l3_test_common
from neutron.tests.common import net_helpers
from neutron.tests.functional.agent.l3 import framework
from neutron_lib import constants
from neutron_lib.utils import net as n_utils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import uuidutils
import testtools

from neutron_vpnaas.services.vpn import agent as vpn_agent
from neutron_vpnaas.services.vpn.agent import vpn_agent_opts
from neutron_vpnaas.services.vpn.device_drivers import ipsec


_uuid = uuidutils.generate_uuid
FAKE_IKE_POLICY = {
    'auth_algorithm': 'sha1',
    "ike_version": "v1",
    'encryption_algorithm': 'aes-128',
    'pfs': 'group5',
    'phase1_negotiation_mode': 'main',
    'lifetime_units': 'seconds',
    'lifetime_value': 3600
}

FAKE_IPSEC_POLICY = {
    "encapsulation_mode": "tunnel",
    "encryption_algorithm": "aes-128",
    "pfs": "group5",
    "lifetime_units": "seconds",
    "lifetime_value": 3600,
    "transform_protocol": "esp",
    "auth_algorithm": "sha1",
}

FAKE_VPN_SERVICE = {
    "id": _uuid(),
    "router_id": _uuid(),
    "status": constants.PENDING_CREATE,
    "admin_state_up": True,
    'external_ip': "172.24.4.8"
}

FAKE_IPSEC_CONNECTION = {
    "vpnservice_id": _uuid(),
    "status": "PENDING_CREATE",
    "psk": "969022489",
    "initiator": "bi-directional",
    "admin_state_up": True,
    "auth_mode": "psk",
    'external_ip': "172.24.4.8",
    "peer_cidrs": ["10.100.255.224/28"],
    "mtu": 1500,
    "dpd_action": "hold",
    "dpd_interval": 30,
    "dpd_timeout": 120,
    "route_mode": "static",
    "ikepolicy": FAKE_IKE_POLICY,
    "ipsecpolicy": FAKE_IPSEC_POLICY,
    "peer_address": "172.24.4.8",
    "peer_id": "172.24.4.8",
    "id": _uuid()
}


FAKE_IKE_POLICY_SHA256 = {
    'auth_algorithm': 'sha256',
    "ike_version": "v1",
    'encryption_algorithm': 'aes-128',
    'pfs': 'group5',
    'phase1_negotiation_mode': 'main',
    'lifetime_units': 'seconds',
    'lifetime_value': 3600
}

FAKE_IPSEC_POLICY_SHA256 = {
    "encapsulation_mode": "tunnel",
    "encryption_algorithm": "aes-128",
    "pfs": "group5",
    "lifetime_units": "seconds",
    "lifetime_value": 3600,
    "transform_protocol": "esp",
    "auth_algorithm": "sha256",
}

FAKE_IPSEC_CONNECTION_SHA256 = {
    "vpnservice_id": _uuid(),
    "status": "PENDING_CREATE",
    "psk": "969022489",
    "initiator": "bi-directional",
    "admin_state_up": True,
    "auth_mode": "psk",
    'external_ip': "172.24.4.8",
    "peer_cidrs": ["10.100.255.224/28"],
    "mtu": 1500,
    "dpd_action": "hold",
    "dpd_interval": 30,
    "dpd_timeout": 120,
    "route_mode": "static",
    "ikepolicy": FAKE_IKE_POLICY_SHA256,
    "ipsecpolicy": FAKE_IPSEC_POLICY_SHA256,
    "peer_address": "172.24.4.8",
    "peer_id": "172.24.4.8",
    "id": _uuid()
}

PUBLIC_NET = netaddr.IPNetwork('19.4.4.0/24')
PRIVATE_NET = netaddr.IPNetwork('35.4.0.0/16')
FAKE_PUBLIC_SUBNET_ID = _uuid()
FAKE_PRIVATE_SUBNET_ID = _uuid()

FAKE_ROUTER = {
    'enable_snat': True,
    'gw_port': {
        'network_id': _uuid(),
        'subnets': [
            {
                'cidr': str(PUBLIC_NET),
                'gateway_ip': str(PUBLIC_NET[1]),
                'id': FAKE_PUBLIC_SUBNET_ID
            }
        ],
        'fixed_ips': [
            {
                'subnet_id': FAKE_PUBLIC_SUBNET_ID,
                'prefixlen': PUBLIC_NET.prefixlen,
            }
        ],
    },
    'distributed': False,
    '_floatingips': [],
    'routes': []
}

# It's a long name.
NON_ASCII_VPNSERVICE_NAME = '\u9577\u3044\u540d\u524d\u3067\u3059'
# I'm doing very well.
NON_ASCII_PSK = '\u00e7a va tr\u00e9s bien'


def get_ovs_bridge(br_name):
    return ovs_lib.OVSBridge(br_name)


Vm = collections.namedtuple('Vm', ['namespace', 'port_ip'])


class SiteInfo:

    """Holds info on the router, ports, service, and connection."""

    def __init__(self, public_net, private_nets):
        self.public_net = public_net
        self.private_nets = private_nets
        self.generate_router_info()
        self._prepare_vpn_service_info()

    def _get_random_mac(self):
        mac_base = cfg.CONF.base_mac.split(':')
        return n_utils.get_random_mac(mac_base)

    def _generate_private_interface_for_router(self, subnet):
        subnet_id = _uuid()
        return {
            'id': _uuid(),
            'admin_state_up': True,
            'network_id': _uuid(),
            'mtu': 1500,
            'mac_address': self._get_random_mac(),
            'subnets': [
                {
                    'ipv6_ra_mode': None,
                    'cidr': str(subnet),
                    'gateway_ip': str(subnet[1]),
                    'id': subnet_id,
                    'ipv6_address_mode': None
                }
            ],
            'fixed_ips': [
                {
                    'subnet_id': subnet_id,
                    'prefixlen': 24,
                    'ip_address': str(subnet[4])
                }
            ]
        }

    def generate_router_info(self):
        self.info = copy.deepcopy(FAKE_ROUTER)
        self.info['id'] = _uuid()
        self.info['project_id'] = _uuid()
        self.info['_interfaces'] = [
            self._generate_private_interface_for_router(subnet)
            for subnet in self.private_nets]
        self.info['gw_port']['id'] = _uuid()
        self.info['gw_port']['fixed_ips'][0]['ip_address'] = str(
            self.public_net)
        self.info['gw_port']['mac_address'] = self._get_random_mac()
        self.info['ha'] = False

    def _prepare_vpn_service_info(self):
        self.vpn_service = copy.deepcopy(FAKE_VPN_SERVICE)
        self.vpn_service.update({'id': _uuid(),
                                 'router_id': self.info['id'],
                                 'external_ip': str(self.public_net)})

    def prepare_ipsec_conn_info(self, peer, connection=FAKE_IPSEC_CONNECTION,
                                local_id=None, peer_id=None):
        ipsec_connection = copy.deepcopy(connection)
        local_cidrs = [str(s) for s in self.private_nets]
        peer_cidrs = [str(s) for s in peer.private_nets]
        ipsec_connection.update({
            'id': _uuid(),
            'vpnservice_id': self.vpn_service['id'],
            'external_ip': self.vpn_service['external_ip'],
            'peer_cidrs': peer_cidrs,
            'peer_address': peer.vpn_service['external_ip'],
            'peer_id': peer.vpn_service['external_ip'],
            'local_cidrs': local_cidrs,
            'local_ip_vers': 4
        })
        if local_id:
            ipsec_connection['local_id'] = local_id
        if peer_id:
            ipsec_connection['peer_id'] = peer_id
        self.vpn_service['ipsec_site_connections'] = [ipsec_connection]


class SiteInfoWithHaRouter(SiteInfo):

    def __init__(self, public_net, private_nets, host, failover_host):
        self.host = host
        self.failover_host = failover_host
        self.get_ns_name = mock.patch.object(n_namespaces.RouterNamespace,
                                             '_get_ns_name').start()
        super().__init__(public_net, private_nets)

    def generate_router_info(self):
        super().generate_router_info()
        self.info['ha'] = True
        self.info['ha_vr_id'] = 1
        self.info[constants.HA_INTERFACE_KEY] = (
            l3_test_common.get_ha_interface())
        # Mock router namespace name, for when router is created
        self.get_ns_name.return_value = "qrouter-{}-{}".format(
            self.info['id'], self.host)

    def generate_backup_router_info(self):
        # Clone router info, using different HA interface (using same ID)
        info = copy.deepcopy(self.info)
        info[constants.HA_INTERFACE_KEY] = (
            l3_test_common.get_ha_interface(ip='169.254.192.2',
                                            mac='22:22:22:22:22:22'))
        # Mock router namespace name, for when router is created
        self.get_ns_name.return_value = "qrouter-{}-{}".format(
            info['id'], self.failover_host)
        return info


class TestIPSecBase(framework.L3AgentTestFramework):
    NESTED_NAMESPACE_SEPARATOR = '@'

    def setUp(self):
        super().setUp()
        common_config.register_common_config_options()
        mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.'
                   'IPsecVpnDriverApi').start()
        # avoid report_status running periodically
        mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall').start()
        # Both the vpn agents try to use execute_rootwrap_daemon's socket
        # simultaneously during test cleanup, but execute_rootwrap_daemon has
        # limitations with simultaneous reads. So avoid using
        # root_helper_daemon and instead use root_helper
        # https://bugs.launchpad.net/neutron/+bug/1482622
        cfg.CONF.set_override('root_helper_daemon', None, group='AGENT')

        # Mock the method below because it causes Exception:
        #   RuntimeError: Second simultaneous read on fileno 5 detected.
        #   Unless you really know what you're doing, make sure that only
        #   one greenthread can read any particular socket.  Consider using
        #   a pools.Pool. If you do know what you're doing and want to disable
        #   this error, call eventlet.debug.hub_prevent_multiple_readers(False)
        # Can reproduce the exception in the test only
        ip_lib.send_ip_addr_adv_notif = mock.Mock()

        self.vpn_agent = vpn_agent.L3WithVPNaaS(self.conf)
        self.driver = self.vpn_agent.device_drivers[0]
        self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock(
            return_value=[])
        self.driver.report_status = mock.Mock()

        self.private_nets = list(PRIVATE_NET.subnet(24))

    def _connect_agents(self, agent1, agent2):
        """Simulate both agents in the same host.

         For packet flow between resources connected to these two agents,
         agent's ovs bridges are connected through patch ports.
        """
        br_int_1 = get_ovs_bridge(agent1.conf.OVS.integration_bridge)
        br_int_2 = get_ovs_bridge(agent2.conf.OVS.integration_bridge)
        net_helpers.create_patch_ports(br_int_1, br_int_2)

    def _get_config_opts(self):
        """Register default config options"""
        config = cfg.ConfigOpts()
        config.register_opts(conf_common.core_opts)
        config.register_opts(conf_common.core_cli_opts)
        config.register_opts(serviceprovider_opts, 'service_providers')
        config.register_opts(vpn_agent_opts, 'vpnagent')
        config.register_opts(ipsec.ipsec_opts, 'ipsec')
        config.register_opts(ipsec.openswan_opts, 'openswan')

        logging.register_options(config)
        agent_config.register_process_monitor_opts(config)
        ext_manager.register_opts(config)
        return config

    def _configure_agent(self, host):
        """Override specific config options"""
        config = self._get_config_opts()
        l3_agent_main.register_opts(config)
        cfg.CONF.set_override('debug', True)
        agent_config.setup_logging()
        config.set_override('extensions', ['vpnaas'], 'agent')
        config.set_override(
            'interface_driver',
            'neutron.agent.linux.interface.OVSInterfaceDriver')

        br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge
        config.set_override('integration_bridge', br_int.br_name, 'OVS')

        temp_dir = self.get_new_temp_dir()
        get_temp_file_path = functools.partial(self.get_temp_file_path,
                                               root=temp_dir)
        config.set_override('state_path', temp_dir.path)
        config.set_override('metadata_proxy_socket',
                            get_temp_file_path('metadata_proxy'))
        config.set_override('ha_confs_path',
                            get_temp_file_path('ha_confs'))
        config.set_override('external_pids',
                            get_temp_file_path('external/pids'))
        config.set_override('host', host)
        ipsec_config_base_dir = '{}/{}'.format(temp_dir.path, 'ipsec')
        config.set_override('config_base_dir',
                            ipsec_config_base_dir, group='ipsec')

        # Assign ip address to br-int port because it is a gateway
        ex_port = ip_lib.IPDevice(br_int.br_name)
        ex_port.addr.add(str(PUBLIC_NET[1]))

        return config

    def _setup_failover_agent(self):
        self.failover_agent = self._configure_agent('agent2')
        self._connect_agents(self.vpn_agent, self.failover_agent)
        self.failover_driver = self.failover_agent.device_drivers[0]
        self.failover_driver.agent_rpc.get_vpn_services_on_host = (
            mock.Mock(return_value=[]))
        self.failover_driver.report_status = mock.Mock()

    def create_router(self, agent, info):
        """Create router for agent from router info."""
        self.addCleanup(agent._safe_router_removed, info['id'])

        # Generate unique internal and external router device names using the
        # agent's hostname. This is to allow multiple HA router replicas to
        # co-exist on the same machine, otherwise they'd all use the same
        # device names and OVS would freak out(OVS won't allow a port with
        # same name connected to two bridges).
        def _append_suffix(dev_name):
            # If dev_name = 'xyz123' and the suffix is 'agent2' then the result
            # will be 'xy-nt2'
            return f"{dev_name[:-4]}-{agent.host[-3:]}"

        def get_internal_device_name(port_id):
            return _append_suffix(
                (n_namespaces.INTERNAL_DEV_PREFIX + port_id)
                [:interface.LinuxInterfaceDriver.DEV_NAME_LEN])

        def get_external_device_name(port_id):
            return _append_suffix(
                (n_namespaces.EXTERNAL_DEV_PREFIX + port_id)
                [:interface.LinuxInterfaceDriver.DEV_NAME_LEN])

        mock_get_internal_device_name = mock.patch.object(
            router_info.RouterInfo, 'get_internal_device_name').start()
        mock_get_internal_device_name.side_effect = get_internal_device_name
        mock_get_external_device_name = mock.patch.object(
            router_info.RouterInfo, 'get_external_device_name').start()
        mock_get_external_device_name.side_effect = get_external_device_name

        # NOTE(huntxu): with commit 88f5e11d8bf, neutron plugs new ports as
        # dead vlan(4095). During functional tests, all the ports are untagged.
        # So need to remove such tag during functional testing.
        original_plug_new = interface.OVSInterfaceDriver.plug_new

        def plug_new(self, *args, **kwargs):
            original_plug_new(self, *args, **kwargs)
            bridge = (kwargs.get('bridge') or args[4] or
                      self.conf.OVS.integration_bridge)
            device_name = kwargs.get('device_name') or args[2]
            ovsbr = ovs_lib.OVSBridge(bridge)
            ovsbr.clear_db_attribute('Port', device_name, 'tag')

        with mock.patch(
            'neutron.agent.linux.interface.OVSInterfaceDriver.plug_new',
            autospec=True
        ) as ovs_plug_new:
            ovs_plug_new.side_effect = plug_new
            agent._process_added_router(info)

        return agent.router_info[info['id']]

    def _port_first_ip_cidr(self, port):
        fixed_ip = port['fixed_ips'][0]
        return common_utils.ip_to_cidr(fixed_ip['ip_address'],
                                       fixed_ip['prefixlen'])

    def create_ports_for(self, site):
        """Creates namespaces and ports for simulated VM.

        There will be a unique namespace for each port, which is representing
        a VM for the test.
        """
        bridge = get_ovs_bridge(self.vpn_agent.conf.OVS.integration_bridge)
        site.vm = []
        for internal_port in site.router.internal_ports:
            router_ip_cidr = self._port_first_ip_cidr(internal_port)
            port_ip_cidr = net_helpers.increment_ip_cidr(router_ip_cidr, 1)
            client_ns = self.useFixture(
                net_helpers.NamespaceFixture()).ip_wrapper
            namespace = client_ns.namespace
            port = self.useFixture(
                net_helpers.OVSPortFixture(bridge, namespace)).port
            port.addr.add(port_ip_cidr)
            port.route.add_gateway(router_ip_cidr.partition('/')[0])
            site.vm.append(Vm(namespace, port_ip_cidr.partition('/')[0]))

    def create_site(self, public_net, private_nets, l3ha=False):
        """Build router(s), namespaces, and ports for a site.

        For HA, we'll create a backup router and wait for both routers
        to be ready, so that we can test pings after failover.
        """
        if l3ha:
            site = SiteInfoWithHaRouter(public_net, private_nets,
                                        self.agent.host,
                                        self.failover_agent.host)
        else:
            site = SiteInfo(public_net, private_nets)

        site.router = self.create_router(self.agent, site.info)
        if l3ha:
            backup_info = site.generate_backup_router_info()
            site.backup_router = self.create_router(self.failover_agent,
                                                    backup_info)
            linux_utils.wait_until_true(
                lambda: site.router.ha_state in ('master', 'primary'))
            linux_utils.wait_until_true(
                lambda: site.backup_router.ha_state == 'backup')

        self.create_ports_for(site)
        return site

    def prepare_ipsec_site_connections(self, site1, site2):
        """Builds info for connections in both directions in prep for sync."""
        site1.prepare_ipsec_conn_info(site2)
        site2.prepare_ipsec_conn_info(site1)

    def prepare_ipsec_site_connections_sha256(self, site1, site2):
        """Builds info for connections in both directions in prep for sync."""
        site1.prepare_ipsec_conn_info(site2,
                                      FAKE_IPSEC_CONNECTION_SHA256)
        site2.prepare_ipsec_conn_info(site1,
                                      FAKE_IPSEC_CONNECTION_SHA256)

    def prepare_ipsec_site_connections_local_id(self, site1, site2):
        """Builds info for connections in both directions in prep for sync."""
        site1.prepare_ipsec_conn_info(site2, local_id='@site1.com',
                                      peer_id='@site2.com')
        site2.prepare_ipsec_conn_info(site1, local_id='@site2.com',
                                      peer_id='@site1.com')

    def sync_to_create_ipsec_connections(self, site1, site2):
        """Perform a sync, so that connections are created."""
        # Provide service info to sync
        self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock(
            return_value=[site1.vpn_service, site2.vpn_service])

        local_router_id = site1.router.router_id
        peer_router_id = site2.router.router_id
        self.driver.sync(mock.Mock(), [site1.router,
                                       site2.router])
        self.agent._process_updated_router(site1.router.router)
        self.agent._process_updated_router(site2.router.router)
        self.addCleanup(self.driver._delete_vpn_processes,
                        [local_router_id, peer_router_id], [])

    def sync_failover_agent(self, site):
        """Perform a sync on failover agent associated w/backup router."""
        self.failover_driver.agent_rpc.get_vpn_services_on_host = mock.Mock(
            return_value=[site.vpn_service])
        self.failover_driver.sync(mock.Mock(), [site.router])

    def check_ping(self, from_site, to_site, instance=0, success=True):
        if success:
            net_helpers.assert_ping(from_site.vm[instance].namespace,
                                    to_site.vm[instance].port_ip,
                                    timeout=8, count=4)
        else:
            net_helpers.assert_no_ping(from_site.vm[instance].namespace,
                                       to_site.vm[instance].port_ip,
                                       timeout=8, count=4)

    def _failover_ha_router(self, router1, router2):
        """Cause a failover of HA router.

        Fail the agent1's HA router. Agent1's HA router will transition
        to backup and agent2's HA router will become master. Wait for
        the failover to complete.
        """
        device_name = router1.get_ha_device_name()
        ha_device = ip_lib.IPDevice(device_name, router1.ns_name)
        ha_device.link.set_down()
        linux_utils.wait_until_true(
            lambda: router2.ha_state in ('master', 'primary'))
        linux_utils.wait_until_true(lambda: router1.ha_state == 'backup')

    def _ipsec_process_exists(self, conf, router, pid_files):
        """Check if *Swan process has started up."""
        for pid_file in pid_files:
            pm = external_process.ProcessManager(
                conf,
                "ipsec",
                router.ns_name, pid_file=pid_file)
            if pm.active:
                break
        return pm.active

    def _wait_for_ipsec_startup(self, router, driver, conf, should_run=True):
        """Wait for new IPSec process on failover agent to start up."""
        # check for both strongswan and openswan processes
        path = driver.processes[router.router_id].config_dir
        pid_files = ['%s/var/run/charon.pid' % path,
                     '%s/var/run/pluto.pid' % path]
        linux_utils.wait_until_true(
            lambda: should_run == self._ipsec_process_exists(
                conf, router, pid_files))

    @staticmethod
    def _update_vpnservice(site, **kwargs):
        site.vpn_service.update(kwargs)

    @staticmethod
    def _update_ipsec_connection(site, **kwargs):
        ipsec_connection = site.vpn_service['ipsec_site_connections'][0]
        ipsec_connection.update(kwargs)


class TestIPSecScenario(TestIPSecBase):

    @testtools.skip('bug/1598466')
    def test_single_ipsec_connection(self):
        site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]])
        site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]])

        self.check_ping(site1, site2, success=False)
        self.check_ping(site2, site1, success=False)

        self.prepare_ipsec_site_connections(site1, site2)
        self.sync_to_create_ipsec_connections(site1, site2)

        self.check_ping(site1, site2)
        self.check_ping(site2, site1)

    @testtools.skip('bug/1598466')
    def test_single_ipsec_connection_sha256(self):
        site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]])
        site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]])

        self.check_ping(site1, site2, success=False)
        self.check_ping(site2, site1, success=False)

        self.prepare_ipsec_site_connections_sha256(site1, site2)
        self.sync_to_create_ipsec_connections(site1, site2)

        self.check_ping(site1, site2)
        self.check_ping(site2, site1)

    @testtools.skip('bug/1598466')
    def test_single_ipsec_connection_local_id(self):
        site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]])
        site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]])

        self.check_ping(site1, site2, success=False)
        self.check_ping(site2, site1, success=False)

        self.prepare_ipsec_site_connections_local_id(site1, site2)
        self.sync_to_create_ipsec_connections(site1, site2)

        self.check_ping(site1, site2)
        self.check_ping(site2, site1)

    @testtools.skip('bug/1598466')
    def test_ipsec_site_connections_with_mulitple_subnets(self):
        """Check with a pair of subnets on each end of connection."""
        site1 = self.create_site(PUBLIC_NET[4], self.private_nets[1:3])
        site2 = self.create_site(PUBLIC_NET[5], self.private_nets[3:5])

        # Just check from each VM, not every combination
        for i in [0, 1]:
            self.check_ping(site1, site2, instance=i, success=False)
            self.check_ping(site2, site1, instance=i, success=False)

        self.prepare_ipsec_site_connections(site1, site2)
        self.sync_to_create_ipsec_connections(site1, site2)

        for i in [0, 1]:
            self.check_ping(site1, site2, instance=i)
            self.check_ping(site2, site1, instance=i)

    @testtools.skip('bug/1598466')
    def test_ipsec_site_connections_with_l3ha_routers(self):
        """Test ipsec site connection with HA routers.
        This test creates two agents. First agent will have Legacy and HA
        routers. Second agent will host only HA router. We setup ipsec
        connection between legacy and HA router.

        When HA router is created, agent1 will have master router and
        agent2 will have backup router. Ipsec connection will be established
        between legacy router and agent1's master HA router.

        Then we fail the agent1's master HA router. Agent1's HA router will
        transition to backup and agent2's HA router will become master.
        Now ipsec connection will be established between legacy router and
        agent2's master HA router
        """

        self._setup_failover_agent()

        site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]])
        site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]],
                                 l3ha=True)

        # No ipsec connection between legacy router and HA routers
        self.check_ping(site1, site2, 0, success=False)
        self.check_ping(site2, site1, 0, success=False)

        self.prepare_ipsec_site_connections(site1, site2)
        self.sync_to_create_ipsec_connections(site1, site2)
        self.sync_failover_agent(site2)

        # Test ipsec connection between legacy router and agent2's HA router
        self.check_ping(site1, site2, 0)
        self.check_ping(site2, site1, 0)

        self._failover_ha_router(site2.router, site2.backup_router)
        self._wait_for_ipsec_startup(site2.backup_router,
                                     self.failover_driver,
                                     self.failover_agent.conf)

        # Test ipsec connection between legacy router and agent2's HA router
        self.check_ping(site1, site2, 0)
        self.check_ping(site2, site1, 0)

    @testtools.skip('bug/1598466')
    def _test_admin_state_up(self, update_method):
        # Create ipsec connection between two sites
        site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]])
        site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]])

        self.prepare_ipsec_site_connections(site1, site2)
        self.sync_to_create_ipsec_connections(site1, site2)

        self.check_ping(site1, site2)
        self.check_ping(site2, site1)

        # Disable resource on one of the sites and check that
        # ping no longer passes.
        update_method(site1, admin_state_up=False)
        self.sync_to_create_ipsec_connections(site1, site2)

        self.check_ping(site1, site2, 0, success=False)
        self.check_ping(site2, site1, 0, success=False)

        # Validate that ipsec process for the disabled site was terminated.
        self._wait_for_ipsec_startup(site1.router, self.driver,
                                     self.vpn_agent.conf,
                                     should_run=False)

        # Change admin_state_up of the disabled resource back to True and
        # check that everything works again.
        update_method(site1, admin_state_up=True)
        self.sync_to_create_ipsec_connections(site1, site2)

        self.check_ping(site1, site2)
        self.check_ping(site2, site1)

    @testtools.skip('bug/1598466')
    def test_ipsec_site_connections_update_admin_state_up(self):
        """Test updating admin_state_up of ipsec site connections."""

        self._test_admin_state_up(self._update_ipsec_connection)

    @testtools.skip('bug/1598466')
    def test_vpnservice_update_admin_state_up(self):
        """Test updating admin_state_up of a vpn service."""

        self._test_admin_state_up(self._update_vpnservice)
