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


import copy
from mock import patch
import six
import unittest

from networking_sfc.db import sfc_db
from networking_sfc.services.flowclassifier import plugin as fc_plugin

import neutron.conf.common as q_config
from neutron.db import db_base_plugin_v2
import neutron_lib.context as q_context
from neutron_lib.plugins import directory
from neutronclient.common import exceptions as client_exceptions

from oslo_config import cfg
from oslo_utils import uuidutils

from tricircle.common import client
from tricircle.common import constants
from tricircle.common import context
import tricircle.db.api as db_api
from tricircle.db import core
from tricircle.db import models
import tricircle.network.central_fc_driver as fc_driver
from tricircle.network import central_plugin
import tricircle.network.central_sfc_driver as sfc_driver
import tricircle.network.central_sfc_plugin as sfc_plugin
from tricircle.network import helper
import tricircle.tests.unit.utils as test_utils
from tricircle.xjob import xmanager


_resource_store = test_utils.get_resource_store()
TOP_PORTS = _resource_store.TOP_PORTS
TOP_PORTPAIRS = _resource_store.TOP_SFC_PORT_PAIRS
TOP_PORTPAIRGROUPS = _resource_store.TOP_SFC_PORT_PAIR_GROUPS
TOP_PORTCHAINS = _resource_store.TOP_SFC_PORT_CHAINS
TOP_FLOWCLASSIFIERS = _resource_store.TOP_SFC_FLOW_CLASSIFIERS
TOP_CHAIN_GROUP_ASSOCS = _resource_store.TOP_SFC_CHAIN_GROUP_ASSOCIATIONS
TOP_CHAIN_CLASSIFIER_ASSOCS = (
    _resource_store.TOP_SFC_CHAIN_CLASSIFIER_ASSOCIATIONS)
BOTTOM1_PORTS = _resource_store.BOTTOM1_PORTS
BOTTOM2_PORTS = _resource_store.BOTTOM2_PORTS
BOTTOM1_PORTPAIRS = _resource_store.BOTTOM1_SFC_PORT_PAIRS
BOTTOM2_PORTPAIRS = _resource_store.BOTTOM2_SFC_PORT_PAIRS
BOTTOM1_PORTPAIRGROUPS = _resource_store.BOTTOM1_SFC_PORT_PAIR_GROUPS
BOTTOM2_PORTPAIRGROUPS = _resource_store.BOTTOM2_SFC_PORT_PAIR_GROUPS
BOTTOM1_PORTCHAINS = _resource_store.BOTTOM1_SFC_PORT_CHAINS
BOTTOM2_PORTCHAINS = _resource_store.BOTTOM2_SFC_PORT_CHAINS
BOTTOM1_FLOWCLASSIFIERS = _resource_store.BOTTOM1_SFC_FLOW_CLASSIFIERS
BOTTOM2_FLOWCLASSIFIERS = _resource_store.BOTTOM2_SFC_FLOW_CLASSIFIERS
TEST_TENANT_ID = test_utils.TEST_TENANT_ID
DotDict = test_utils.DotDict


class FakeNetworkHelper(helper.NetworkHelper):
    def __init__(self):
        super(FakeNetworkHelper, self).__init__()

    def _get_client(self, region_name=None):
        return FakeClient(region_name)


class FakeBaseXManager(xmanager.XManager):
    def __init__(self):
        self.clients = {constants.TOP: client.Client()}
        self.helper = FakeNetworkHelper()

    def _get_client(self, region_name=None):
        return FakeClient(region_name)

    def sync_service_function_chain(self, ctx, payload):
        (b_pod_id, t_port_chain_id, net_id) = payload[
            constants.JT_SFC_SYNC].split('#')

        if b_pod_id == constants.POD_NOT_SPECIFIED:
            mappings = db_api.get_bottom_mappings_by_top_id(
                ctx, net_id, constants.RT_NETWORK)
            b_pods = [mapping[0] for mapping in mappings]
            for b_pod in b_pods:
                payload = '%s#%s#%s' % (b_pod['pod_id'], t_port_chain_id,
                                        net_id)
                super(FakeBaseXManager, self).sync_service_function_chain(
                    ctx, {constants.JT_SFC_SYNC: payload})
        else:
            super(FakeBaseXManager, self).sync_service_function_chain(
                ctx, payload)


class FakeXManager(FakeBaseXManager):
    def __init__(self, fake_plugin):
        super(FakeXManager, self).__init__()
        self.xjob_handler = FakeBaseRPCAPI(fake_plugin)


class FakeBaseRPCAPI(object):
    def __init__(self, fake_plugin):
        self.xmanager = FakeBaseXManager()

    def sync_service_function_chain(self, ctxt, project_id, portchain_id,
                                    net_id, pod_id):
        combine_id = '%s#%s#%s' % (pod_id, portchain_id, net_id)
        self.xmanager.sync_service_function_chain(
            ctxt,
            payload={constants.JT_SFC_SYNC: combine_id})

    def recycle_resources(self, ctx, project_id):
        self.xmanager.recycle_resources(ctx, payload={
            constants.JT_RESOURCE_RECYCLE: project_id})


class FakeRPCAPI(FakeBaseRPCAPI):
    def __init__(self, fake_plugin):
        self.xmanager = FakeXManager(fake_plugin)


class FakeClient(test_utils.FakeClient):

    def delete_resources(self, _type, ctx, _id):
        if _type == constants.RT_PORT_PAIR:
            pp = self.get_resource(constants.RT_PORT_PAIR, ctx, _id)
            if not pp:
                raise client_exceptions.NotFound()
            if pp['portpairgroup_id']:
                raise client_exceptions.Conflict(constants.STR_IN_USE)
        elif _type == constants.RT_FLOW_CLASSIFIER:
            pc_list = self._res_map[self.region_name][constants.RT_PORT_CHAIN]
            for pc in pc_list:
                if _id in pc['flow_classifiers']:
                    raise client_exceptions.Conflict(constants.STR_IN_USE)

        return super(FakeClient, self).delete_resources(_type, ctx, _id)

    def create_resources(self, _type, ctx, body):
        if _type == constants.RT_PORT_PAIR:
            pp_list = self._res_map[self.region_name][constants.RT_PORT_PAIR]
            for pp in pp_list:
                if body[_type]['ingress'] == pp['ingress']:
                    raise client_exceptions.BadRequest(constants.STR_USED_BY)
        elif _type == constants.RT_PORT_PAIR_GROUP:
            ppg_list = self._res_map[self.region_name][
                constants.RT_PORT_PAIR_GROUP]
            for pp in body[_type]['port_pairs']:
                for ppg in ppg_list:
                    if pp in ppg['port_pairs']:
                        raise client_exceptions.Conflict(constants.STR_IN_USE)
        elif _type == constants.RT_FLOW_CLASSIFIER:
            fc_list = self._res_map[self.region_name][
                constants.RT_FLOW_CLASSIFIER]
            for fc in fc_list:
                if (body[_type]['logical_source_port'] ==
                        fc['logical_source_port']):
                    raise client_exceptions.BadRequest(
                        constants.STR_CONFLICTS_WITH)
        elif _type == constants.RT_PORT_CHAIN:
            pc_list = self._res_map[self.region_name][constants.RT_PORT_CHAIN]
            for fc in body[_type]['flow_classifiers']:
                for pc in pc_list:
                    if fc in pc['flow_classifiers']:
                        raise client_exceptions.Conflict(constants.STR_IN_USE)

        return super(FakeClient, self).create_resources(_type, ctx, body)

    def get_port_chains(self, ctx, portchain_id):
        res = self.get_resource('port_chain', ctx, portchain_id)
        return copy.copy(res) if res else res

    def get_port_pair_groups(self, ctx, portpairgroup_id):
        res = self.get_resource('port_pair_group', ctx, portpairgroup_id)
        return copy.copy(res) if res else res

    def get_flow_classifiers(self, ctx, flowclassifier_id):
        res = self.get_resource('flow_classifier', ctx, flowclassifier_id)
        return copy.copy(res) if res else res

    def list_port_pairs(self, ctx, filters=None, _copy=True):
        portpairs = self.list_resources('port_pair', ctx, filters)
        portpairs_copy = [copy.copy(pp) for pp in portpairs]
        return portpairs_copy if _copy else portpairs

    def list_flow_classifiers(self, ctx, filters=None):
        return self.list_resources('flow_classifier', ctx, filters)

    def list_port_chains(self, ctx, filters=None):
        return self.list_resources('port_chain', ctx, filters)

    def list_port_pair_groups(self, ctx, filters=None):
        return self.list_resources('port_pair_group', ctx, filters)

    def update_port_pairs(self, ctx, portpair_id, body):
        return self.update_resources('port_pair', ctx,
                                     portpair_id, body)

    def update_port_pair_groups(self, ctx, portpairgroup_id, body):
        filters = [{'key': 'portpairgroup_id',
                    'comparator': 'eq',
                    'value': portpairgroup_id}]
        pps = self.list_port_pairs(ctx, filters, False)
        for pp in pps:
            pp['portpairgroup_id'] = None
        return self.update_resources('port_pair_group', ctx,
                                     portpairgroup_id, body)

    def update_flow_classifiers(self, ctx, flowclassifier_id, body):
        return self.update_resources('flow_classifier', ctx,
                                     flowclassifier_id, body)

    def update_port_chains(self, ctx, portchain_id, body):
        return self.update_resources('port_chain', ctx,
                                     portchain_id, body)

    def get_ports(self, ctx, port_id):
        return self.get_resource('port', ctx, port_id)

    def delete_port_chains(self, context, portchain_id):
        pc = self.get_resource('port_chain', context, portchain_id)
        if not pc:
            raise client_exceptions.NotFound()
        self.delete_resources('port_chain', context, portchain_id)

    def delete_port_pairs(self, context, portpair_id):
        pp = self.get_resource('port_pair', context, portpair_id)
        if not pp:
            raise client_exceptions.NotFound()
        pp = self.get_resource('port_pair', context, portpair_id)
        if pp and pp.get('portpairgroup_id'):
            raise client_exceptions.Conflict("in use")
        self.delete_resources('port_pair', context, portpair_id)

    def delete_port_pair_groups(self, context, portpairgroup_id):
        ppg = self.get_resource('port_pair_group', context, portpairgroup_id)
        if not ppg:
            raise client_exceptions.NotFound()
        for pc in BOTTOM1_PORTCHAINS:
            if portpairgroup_id in pc['port_pair_groups']:
                raise client_exceptions.Conflict("in use")
        self.delete_resources('port_pair_group', context, portpairgroup_id)

    def delete_flow_classifiers(self, context, flowclassifier_id):
        fc = self.get_resource('flow_classifier', context, flowclassifier_id)
        if not fc:
            raise client_exceptions.NotFound()
        for pc in BOTTOM1_PORTCHAINS:
            if flowclassifier_id in pc['flow_classifiers']:
                raise client_exceptions.Conflict("in use")
        self.delete_resources('flow_classifier', context, flowclassifier_id)


class FakeNeutronContext(q_context.Context):
    def __init__(self):
        self._session = None
        self.is_admin = True
        self.is_advsvc = False
        self.tenant_id = TEST_TENANT_ID

    @property
    def session(self):
        if not self._session:
            self._session = FakeSession()
        return self._session

    def elevated(self):
        return self


class FakeSession(test_utils.FakeSession):

    def _fill_port_chain_dict(self, port_chain, model_dict, fields=None):
        model_dict['port_pair_groups'] = [
            assoc['portpairgroup_id']
            for assoc in port_chain['chain_group_associations']]
        model_dict['flow_classifiers'] = [
            assoc['flowclassifier_id']
            for assoc in port_chain['chain_classifier_associations']]

    def add_hook(self, model_obj, model_dict):
        if model_obj.__tablename__ == 'sfc_port_chains':
            self._fill_port_chain_dict(model_obj, model_dict)


class FakeDriver(object):
    def __init__(self, driver, name):
        self.obj = driver
        self.name = name


class FakeSfcDriver(sfc_driver.TricircleSfcDriver):
    def __init__(self):
        self.xjob_handler = FakeRPCAPI(self)
        self.helper = helper.NetworkHelper(self)

    def _get_client(self, region_name):
        return FakeClient(region_name)


class FakeFcDriver(fc_driver.TricircleFcDriver):
    def __init__(self):
        self.xjob_handler = FakeRPCAPI(self)
        self.helper = helper.NetworkHelper(self)

    def _get_client(self, region_name):
        return FakeClient(region_name)


class FakeFcPlugin(fc_plugin.FlowClassifierPlugin):
    def __init__(self):
        super(FakeFcPlugin, self).__init__()
        self.driver_manager.ordered_drivers = [FakeDriver(
            FakeFcDriver(), "tricircle_fc")]


class FakeSfcPlugin(sfc_plugin.TricircleSfcPlugin):
    def __init__(self):
        super(FakeSfcPlugin, self).__init__()
        self.driver_manager.ordered_drivers = [FakeDriver(
            FakeSfcDriver(), "tricircle_sfc")]

    def _get_client(self, region_name):
        return FakeClient(region_name)

    def get_port_pairs(self, context, filters=None):
        ret = []
        client = self._get_client('top')
        for key, values in six.iteritems(filters):
            for v in values:
                _filter = [{'key': key, 'comparator': 'eq', 'value': v}]
                res = client.list_resources('port_pair', context, _filter)
                if res:
                    ret.extend(res)
        return ret

    def get_port_chain(self, context, id, fields=None):
        client = self._get_client('top')
        filter = [{'key': 'id', 'comparator': 'eq', 'value': id}]
        portchains = client.list_resources('port_chain', context, filter)
        if portchains:
            return portchains[0]
        return None


def fake_get_context_from_neutron_context(q_context):
    ctx = context.get_db_context()
    ctx.project_id = q_context.project_id
    return ctx


def fake_validate_pps_in_ppg(self, portpairs_list, id=None):
    pass


def fake_make_port_pair_group_dict(self, port_pair_group, fields=None):
    port_pairs = port_pair_group.port_pairs
    if isinstance(port_pairs[0], test_utils.DotDict):
        port_pair_group['port_pairs'] = [pp['id'] for pp in port_pairs]
    return port_pair_group


def fake_make_port_chain_dict(self, port_chain, fields=None):
    port_chain['port_pair_groups'] = [assoc['portpairgroup_id'] for assoc in
                                      port_chain.chain_group_associations]
    port_chain['flow_classifiers'] = [assoc['flowclassifier_id'] for assoc in
                                      port_chain.chain_classifier_associations]
    return port_chain


def fake_make_port_pair_dict(self, port_pair, fields=None):
    return port_pair


class FakeCorePlugin(central_plugin.TricirclePlugin):
    def __init__(self):
        pass

    def get_port(self, ctx, _id):
        return self._get_port(ctx, _id)

    def _get_port(self, ctx, _id):
        top_client = FakeClient()
        _filters = [{'key': 'id', 'comparator': 'eq', 'value': _id}]
        return top_client.list_resources('port', ctx, _filters)[0]


def fake_get_plugin(alias='core'):
    if alias == 'sfc':
        return FakeSfcPlugin()
    return FakeCorePlugin()


class PluginTest(unittest.TestCase):
    def setUp(self):
        core.initialize()
        core.ModelBase.metadata.create_all(core.get_engine())
        cfg.CONF.register_opts(q_config.core_opts)
        core.get_engine().execute('pragma foreign_keys=on')
        self.context = context.Context()
        xmanager.IN_TEST = True
        directory.get_plugin = fake_get_plugin

    def _basic_pod_setup(self):
        pod1 = {'pod_id': 'pod_id_1',
                'region_name': 'pod_1',
                'az_name': 'az_name_1'}
        pod2 = {'pod_id': 'pod_id_2',
                'region_name': 'pod_2',
                'az_name': 'az_name_2'}
        pod3 = {'pod_id': 'pod_id_0',
                'region_name': 'top_pod',
                'az_name': ''}
        for pod in (pod1, pod2, pod3):
            db_api.create_pod(self.context, pod)

    def _prepare_net_test(self, project_id, ctx, pod_name):
        t_net_id = uuidutils.generate_uuid()
        pod_id = 'pod_id_1' if pod_name == 'pod_1' else 'pod_id_2'
        core.create_resource(ctx, models.ResourceRouting,
                             {'top_id': t_net_id,
                              'bottom_id': t_net_id,
                              'pod_id': pod_id,
                              'project_id': project_id,
                              'resource_type': constants.RT_NETWORK})
        return t_net_id

    def _prepare_port_test(self, tenant_id, ctx, pod_name, net_id):
        t_port_id = uuidutils.generate_uuid()
        t_port = {
            'id': t_port_id,
            'network_id': net_id
        }
        TOP_PORTS.append(DotDict(t_port))
        b_port = {
            'id': t_port_id,
            'network_id': net_id
        }
        if pod_name == 'pod_1':
            BOTTOM1_PORTS.append(DotDict(b_port))
        else:
            BOTTOM2_PORTS.append(DotDict(b_port))

        pod_id = 'pod_id_1' if pod_name == 'pod_1' else 'pod_id_2'
        core.create_resource(ctx, models.ResourceRouting,
                             {'top_id': t_port_id,
                              'bottom_id': t_port_id,
                              'pod_id': pod_id,
                              'project_id': tenant_id,
                              'resource_type': constants.RT_PORT})

        return t_port_id

    def _update_port_pair_test(self, ppg_mappings, port_pairs):
        for pp_id, ppg_id in six.iteritems(ppg_mappings):
            for pp in port_pairs:
                if pp['id'] == pp_id:
                    pp['portpairgroup_id'] = ppg_id

    def _prepare_port_pair_test(self, project_id, t_ctx, pod_name,
                                index, ingress, egress, create_bottom,
                                portpairgroup_id=None):
        t_pp_id = uuidutils.generate_uuid()
        b_pp_id = uuidutils.generate_uuid()
        top_pp = {
            'id': t_pp_id,
            'project_id': project_id,
            'tenant_id': project_id,
            'ingress': ingress,
            'egress': egress,
            'name': 'top_pp_%d' % index,
            'service_function_parameters': {
                "weight": 1,
                "correlation": DotDict({'value': 'null'})},
            'description': "description",
            'portpairgroup_id': portpairgroup_id
        }
        TOP_PORTPAIRS.append(DotDict(top_pp))
        if create_bottom:
            btm_pp = {
                'id': b_pp_id,
                'project_id': project_id,
                'tenant_id': project_id,
                'ingress': ingress,
                'egress': egress,
                'name': 'btm_pp_%d' % index,
                'service_function_parameters': {
                    "weight": 1,
                    "correlation": DotDict({'value': 'null'})},
                'description': "description",
                'portpairgroup_id': portpairgroup_id
            }
            if pod_name == 'pod_1':
                BOTTOM1_PORTPAIRS.append(DotDict(btm_pp))
            else:
                BOTTOM2_PORTPAIRS.append(DotDict(btm_pp))

            pod_id = 'pod_id_1' if pod_name == 'pod_1' else 'pod_id_2'
            core.create_resource(t_ctx, models.ResourceRouting,
                                 {'top_id': t_pp_id,
                                  'bottom_id': b_pp_id,
                                  'pod_id': pod_id,
                                  'project_id': project_id,
                                  'resource_type': constants.RT_PORT_PAIR})

        return t_pp_id, b_pp_id

    def _prepare_port_pair_group_test(self, project_id, t_ctx, pod_name, index,
                                      t_pp_ids, create_bottom, b_pp_ids):
        t_ppg_id = uuidutils.generate_uuid()
        b_ppg_id = uuidutils.generate_uuid()

        t_client = FakeClient()
        b_client = FakeClient(pod_name)
        t_pps = [t_client.get_resource(
            'port_pair', t_ctx, e) for e in t_pp_ids]
        if create_bottom:
            b_pps = [b_client.get_resource(
                'port_pair', t_ctx, e) for e in b_pp_ids]

        top_ppg = {
            "group_id": 1,
            "description": "",
            "tenant_id": project_id,
            "port_pair_group_parameters": {"lb_fields": []},
            "port_pairs": t_pps,
            "project_id": project_id,
            "id": t_ppg_id,
            "name": 'top_ppg_%d' % index,
            "tap_enabled": False}
        TOP_PORTPAIRGROUPS.append(DotDict(top_ppg))
        if create_bottom:
            btm_ppg = {
                "group_id": 1,
                "description": "",
                "tenant_id": project_id,
                "port_pair_group_parameters": {"lb_fields": []},
                "port_pairs": b_pps,
                "project_id": project_id,
                "id": b_ppg_id,
                "name": 'btm_ppg_%d' % index,
                "tap_enabled": False}
            if pod_name == 'pod_1':
                BOTTOM1_PORTPAIRGROUPS.append(DotDict(btm_ppg))
            else:
                BOTTOM2_PORTPAIRGROUPS.append(DotDict(btm_ppg))

            pod_id = 'pod_id_1' if pod_name == 'pod_1' else 'pod_id_2'
            core.create_resource(t_ctx, models.ResourceRouting,
                                 {'top_id': t_ppg_id,
                                  'bottom_id': b_ppg_id,
                                  'pod_id': pod_id,
                                  'project_id': project_id,
                                  'resource_type':
                                      constants.RT_PORT_PAIR_GROUP})

        return t_ppg_id, b_ppg_id

    def _prepare_flow_classifier_test(self, project_id, t_ctx, pod_name,
                                      index, src_port_id, create_bottom):
        t_fc_id = uuidutils.generate_uuid()
        b_fc_id = uuidutils.generate_uuid()

        top_fc = {
            "source_port_range_min": None,
            "destination_ip_prefix": None,
            "protocol": None,
            "description": "",
            "l7_parameters": {},
            "source_port_range_max": None,
            "id": t_fc_id,
            "name": "t_fc_%s" % index,
            "ethertype": "IPv4",
            "tenant_id": project_id,
            "source_ip_prefix": "1.0.0.0/24",
            "logical_destination_port": None,
            "destination_port_range_min": None,
            "destination_port_range_max": None,
            "project_id": project_id,
            "logical_source_port": src_port_id}

        TOP_FLOWCLASSIFIERS.append(DotDict(top_fc))
        if create_bottom:
            btm_fc = {
                "source_port_range_min": None,
                "destination_ip_prefix": None,
                "protocol": None,
                "description": "",
                "l7_parameters": {},
                "source_port_range_max": None,
                "id": b_fc_id,
                "name": "b_fc_%s" % index,
                "ethertype": "IPv4",
                "tenant_id": project_id,
                "source_ip_prefix": "1.0.0.0/24",
                "logical_destination_port": None,
                "destination_port_range_min": None,
                "destination_port_range_max": None,
                "project_id": project_id,
                "logical_source_port": src_port_id}
            if pod_name == 'pod_1':
                BOTTOM1_FLOWCLASSIFIERS.append(DotDict(btm_fc))
            else:
                BOTTOM2_FLOWCLASSIFIERS.append(DotDict(btm_fc))

            pod_id = 'pod_id_1' if pod_name == 'pod_1' else 'pod_id_2'
            core.create_resource(t_ctx, models.ResourceRouting,
                                 {'top_id': t_fc_id,
                                  'bottom_id': b_fc_id,
                                  'pod_id': pod_id,
                                  'project_id': project_id,
                                  'resource_type':
                                      constants.RT_FLOW_CLASSIFIER})

        return t_fc_id, b_fc_id

    def _prepare_chain_group_assoc_test(self, chain_id, group_id):
        chain_group_assoc = {'portpairgroup_id': group_id,
                             'portchain_id': chain_id}
        TOP_CHAIN_GROUP_ASSOCS.append(DotDict(chain_group_assoc))

    def _prepare_chain_classifier_assoc_test(self,
                                             chain_id, flowclassifier_id):
        chain_classifier_assoc = {'flowclassifier_id': flowclassifier_id,
                                  'portchain_id': chain_id}
        TOP_CHAIN_CLASSIFIER_ASSOCS.append(DotDict(chain_classifier_assoc))

    def _prepare_port_chain_test(self, project_id, t_ctx, pod_name,
                                 index, create_bottom, ids):
        t_pc_id = uuidutils.generate_uuid()
        b_pc_id = uuidutils.generate_uuid()

        top_pc = {
            "tenant_id": project_id,
            "name": "t_pc_%s" % index,
            "chain_parameters": {
                "symmetric": False, "correlation": "mpls"},
            "port_pair_groups": ids['t_ppg_id'],
            "flow_classifiers": ids['t_fc_id'],
            "project_id": project_id,
            "chain_id": 1,
            "description": "",
            "id": t_pc_id}

        TOP_PORTCHAINS.append(DotDict(top_pc))
        if create_bottom:
            btm_pc = {
                "tenant_id": project_id,
                "name": "b_pc_%s" % index,
                "chain_parameters": {
                    "symmetric": False, "correlation": "mpls"},
                "port_pair_groups": ids['b_ppg_id'],
                "flow_classifiers": ids['b_fc_id'],
                "project_id": project_id,
                "chain_id": 1,
                "description": "",
                "id": b_pc_id}
            if pod_name == 'pod_1':
                BOTTOM1_PORTCHAINS.append(DotDict(btm_pc))
            else:
                BOTTOM2_PORTCHAINS.append(DotDict(btm_pc))

            pod_id = 'pod_id_1' if pod_name == 'pod_1' else 'pod_id_2'
            core.create_resource(t_ctx, models.ResourceRouting,
                                 {'top_id': t_pc_id,
                                  'bottom_id': b_pc_id,
                                  'pod_id': pod_id,
                                  'project_id': project_id,
                                  'resource_type': constants.RT_PORT_CHAIN})

        return t_pc_id, b_pc_id

    def test_get_client(self):
        driver = fc_driver.TricircleFcDriver()
        t_client = driver._get_client('top')
        self.assertEqual(t_client.region_name, 'top')

    @patch.object(context, 'get_context_from_neutron_context',
                  new=fake_get_context_from_neutron_context)
    @patch.object(directory, 'get_plugin', new=fake_get_plugin)
    def test_get_port(self):
        self._basic_pod_setup()
        project_id = TEST_TENANT_ID
        fake_plugin = FakeSfcPlugin()
        t_ctx = context.get_db_context()
        port_id = self._prepare_port_test(project_id, t_ctx, 'pod_1', None)
        port = fake_plugin._get_port(context, port_id)
        self.assertIsNotNone(port)

    @patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_port',
                  new=FakeCorePlugin.get_port)
    @patch.object(sfc_db.SfcDbPlugin, 'get_port_pairs',
                  new=FakeSfcPlugin.get_port_pairs)
    @patch.object(context, 'get_context_from_neutron_context',
                  new=fake_get_context_from_neutron_context)
    def test_create_port_chain(self):
        project_id = TEST_TENANT_ID
        q_ctx = FakeNeutronContext()
        t_ctx = context.get_db_context()
        self._basic_pod_setup()
        fake_plugin = FakeSfcPlugin()

        t_net_id = self._prepare_net_test(project_id, t_ctx, 'pod_1')
        ingress = self._prepare_port_test(project_id, t_ctx, 'pod_1', t_net_id)
        egress = self._prepare_port_test(project_id, t_ctx, 'pod_1', t_net_id)
        src_port_id = self._prepare_port_test(project_id,
                                              t_ctx, 'pod_1', t_net_id)
        t_pp1_id, _ = self._prepare_port_pair_test(
            project_id, t_ctx, 'pod_1', 0, ingress, egress, False)
        t_ppg1_id, _ = self._prepare_port_pair_group_test(
            project_id, t_ctx, 'pod_1', 0, [t_pp1_id], False, None)
        ppg1_mapping = {t_pp1_id: t_ppg1_id}
        self._update_port_pair_test(ppg1_mapping, TOP_PORTPAIRS)
        t_fc1_id, _ = self._prepare_flow_classifier_test(
            project_id, t_ctx, 'pod_1', 0, src_port_id, False)
        body = {"port_chain": {
            "tenant_id": project_id,
            "name": "pc1",
            "chain_parameters": {
                "symmetric": False, "correlation": "mpls"},
            "port_pair_groups": [t_ppg1_id],
            "flow_classifiers": [t_fc1_id],
            "project_id": project_id,
            "chain_id": 1,
            "description": ""}}
        t_pc1 = fake_plugin.create_port_chain(q_ctx, body)
        pp1_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pp1_id, constants.RT_PORT_PAIR)
        ppg1_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_ppg1_id, constants.RT_PORT_PAIR_GROUP)
        fc1_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_fc1_id, constants.RT_FLOW_CLASSIFIER)
        pc1_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pc1['id'], constants.RT_PORT_CHAIN)
        btm1_pp_ids = [btm_pp['id'] for btm_pp in BOTTOM1_PORTPAIRS]
        btm1_ppg_ids = [btm_ppg['id'] for btm_ppg in BOTTOM1_PORTPAIRGROUPS]
        btm1_fc_ids = [btm_fc['id'] for btm_fc in BOTTOM1_FLOWCLASSIFIERS]
        btm1_pc_ids = [btm_pc['id'] for btm_pc in BOTTOM1_PORTCHAINS]
        b_pp1_id = pp1_mappings[0][1]
        b_ppg1_id = ppg1_mappings[0][1]
        b_fc1_id = fc1_mappings[0][1]
        b_pc1_id = pc1_mappings[0][1]
        self.assertEqual([b_pp1_id], btm1_pp_ids)
        self.assertEqual([b_ppg1_id], btm1_ppg_ids)
        self.assertEqual([b_fc1_id], btm1_fc_ids)
        self.assertEqual([b_pc1_id], btm1_pc_ids)

        # make conflict
        TOP_PORTCHAINS.pop()
        TOP_FLOWCLASSIFIERS.pop()
        TOP_PORTPAIRGROUPS.pop()
        TOP_PORTPAIRS.pop()
        b_ppg1_mapping = {b_pp1_id: b_ppg1_id}
        self._update_port_pair_test(b_ppg1_mapping, BOTTOM1_PORTPAIRS)
        db_api.create_recycle_resource(
            t_ctx, t_ppg1_id, constants.RT_PORT_PAIR_GROUP, q_ctx.project_id)

        t_pp2_id, _ = self._prepare_port_pair_test(
            project_id, t_ctx, 'pod_1', 0, ingress, egress, False)
        t_ppg2_id, _ = self._prepare_port_pair_group_test(
            project_id, t_ctx, 'pod_1', 0, [t_pp2_id], False, None)
        ppg2_mapping = {t_pp2_id: t_ppg2_id}
        self._update_port_pair_test(ppg2_mapping, TOP_PORTPAIRS)
        t_fc2_id, _ = self._prepare_flow_classifier_test(
            project_id, t_ctx, 'pod_1', 0, src_port_id, False)
        body2 = {"port_chain": {
            "tenant_id": project_id,
            "name": "pc1",
            "chain_parameters": {
                "symmetric": False, "correlation": "mpls"},
            "port_pair_groups": [t_ppg2_id],
            "flow_classifiers": [t_fc2_id],
            "project_id": project_id,
            "chain_id": 1,
            "description": ""}}
        t_pc2 = fake_plugin.create_port_chain(q_ctx, body2)
        pp2_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pp2_id, constants.RT_PORT_PAIR)
        ppg2_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_ppg2_id, constants.RT_PORT_PAIR_GROUP)
        fc2_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_fc2_id, constants.RT_FLOW_CLASSIFIER)
        pc2_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pc2['id'], constants.RT_PORT_CHAIN)
        btm1_pp_ids = [btm_pp['id'] for btm_pp in BOTTOM1_PORTPAIRS]
        btm1_ppg_ids = [btm_ppg['id'] for btm_ppg in BOTTOM1_PORTPAIRGROUPS]
        btm1_fc_ids = [btm_fc['id'] for btm_fc in BOTTOM1_FLOWCLASSIFIERS]
        btm1_pc_ids = [btm_pc['id'] for btm_pc in BOTTOM1_PORTCHAINS]
        b_pp2_id = pp2_mappings[0][1]
        b_ppg2_id = ppg2_mappings[0][1]
        b_fc2_id = fc2_mappings[0][1]
        b_pc2_id = pc2_mappings[0][1]
        self.assertEqual([b_pp2_id], btm1_pp_ids)
        self.assertEqual([b_ppg2_id], btm1_ppg_ids)
        self.assertEqual([b_fc2_id], btm1_fc_ids)
        self.assertEqual([b_pc2_id], btm1_pc_ids)

    @patch.object(context, 'get_context_from_neutron_context',
                  new=fake_get_context_from_neutron_context)
    def test_delete_port_chain(self):
        project_id = TEST_TENANT_ID
        q_ctx = FakeNeutronContext()
        t_ctx = context.get_db_context()
        self._basic_pod_setup()
        fake_plugin = FakeSfcPlugin()
        ids = {'t_ppg_id': [uuidutils.generate_uuid()],
               'b_ppg_id': [uuidutils.generate_uuid()],
               't_fc_id': [uuidutils.generate_uuid()],
               'b_fc_id': [uuidutils.generate_uuid()]}
        t_pc_id1, _ = self._prepare_port_chain_test(
            project_id, t_ctx, 'pod_1', 0, True, ids)

        fake_plugin.delete_port_chain(q_ctx, t_pc_id1)
        pc_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pc_id1, constants.RT_PORT_CHAIN)
        self.assertEqual(len(TOP_PORTCHAINS), 0)
        self.assertEqual(len(BOTTOM1_PORTCHAINS), 0)
        self.assertEqual(len(pc_mappings), 0)

        t_pc_id2, _ = self._prepare_port_chain_test(
            project_id, t_ctx, 'pod_1', 0, True, ids)
        BOTTOM1_PORTCHAINS.pop()
        fake_plugin.delete_port_chain(q_ctx, t_pc_id2)
        pc_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pc_id2, constants.RT_PORT_CHAIN)
        self.assertEqual(len(TOP_PORTCHAINS), 0)
        self.assertEqual(len(pc_mappings), 0)

    @patch.object(sfc_db.SfcDbPlugin, '_make_port_pair_group_dict',
                  new=fake_make_port_pair_group_dict)
    @patch.object(context, 'get_context_from_neutron_context',
                  new=fake_get_context_from_neutron_context)
    def test_delete_port_pair_group(self):
        project_id = TEST_TENANT_ID
        q_ctx = FakeNeutronContext()
        t_ctx = context.get_db_context()
        self._basic_pod_setup()
        fake_plugin = FakeSfcPlugin()

        t_pp_id = uuidutils.generate_uuid()
        b_pp_id = uuidutils.generate_uuid()

        t_ppg_id1, _ = self._prepare_port_pair_group_test(
            project_id, t_ctx, 'pod_1', 0, [t_pp_id], True, [b_pp_id])
        fake_plugin.delete_port_pair_group(q_ctx, t_ppg_id1)
        ppg_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_ppg_id1, constants.RT_PORT_PAIR_GROUP)
        self.assertEqual(len(TOP_PORTPAIRGROUPS), 0)
        self.assertEqual(len(BOTTOM1_PORTPAIRGROUPS), 0)
        self.assertEqual(len(ppg_mappings), 0)

        t_ppg_id2, _ = self._prepare_port_pair_group_test(
            project_id, t_ctx, 'pod_1', 0, [t_pp_id], True, [b_pp_id])
        BOTTOM1_PORTPAIRGROUPS.pop()
        fake_plugin.delete_port_pair_group(q_ctx, t_ppg_id2)
        ppg_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_ppg_id2, constants.RT_PORT_PAIR_GROUP)
        self.assertEqual(len(TOP_PORTPAIRGROUPS), 0)
        self.assertEqual(len(ppg_mappings), 0)

    @patch.object(sfc_db.SfcDbPlugin, '_make_port_pair_dict',
                  new=fake_make_port_pair_dict)
    @patch.object(context, 'get_context_from_neutron_context',
                  new=fake_get_context_from_neutron_context)
    def test_delete_port_pair(self):
        project_id = TEST_TENANT_ID
        q_ctx = FakeNeutronContext()
        t_ctx = context.get_db_context()
        self._basic_pod_setup()
        fake_plugin = FakeSfcPlugin()

        ingress = uuidutils.generate_uuid()
        egress = uuidutils.generate_uuid()
        t_pp1_id, _ = self._prepare_port_pair_test(
            project_id, t_ctx, 'pod_1', 0, ingress, egress, True)
        fake_plugin.delete_port_pair(q_ctx, t_pp1_id)
        ppg_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pp1_id, constants.RT_PORT_PAIR_GROUP)
        self.assertEqual(len(TOP_PORTPAIRS), 0)
        self.assertEqual(len(BOTTOM1_PORTPAIRS), 0)
        self.assertEqual(len(ppg_mappings), 0)

        t_pp2_id, _ = self._prepare_port_pair_test(
            project_id, t_ctx, 'pod_1', 0, ingress, egress, True)
        BOTTOM1_PORTPAIRS.pop()
        fake_plugin.delete_port_pair(q_ctx, t_pp2_id)
        ppg_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_pp2_id, constants.RT_PORT_PAIR_GROUP)
        self.assertEqual(len(TOP_PORTPAIRS), 0)
        self.assertEqual(len(ppg_mappings), 0)

    @patch.object(context, 'get_context_from_neutron_context',
                  new=fake_get_context_from_neutron_context)
    def test_delete_flow_classifier(self):
        project_id = TEST_TENANT_ID
        q_ctx = FakeNeutronContext()
        t_ctx = context.get_db_context()
        self._basic_pod_setup()
        fake_plugin = FakeFcPlugin()

        src_port_id = uuidutils.generate_uuid()

        t_fc_id1, _ = self._prepare_flow_classifier_test(
            project_id, t_ctx, 'pod_1', 0, src_port_id, True)
        fake_plugin.delete_flow_classifier(q_ctx, t_fc_id1)
        ppg_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_fc_id1, constants.RT_FLOW_CLASSIFIER)
        self.assertEqual(len(TOP_FLOWCLASSIFIERS), 0)
        self.assertEqual(len(BOTTOM1_FLOWCLASSIFIERS), 0)
        self.assertEqual(len(ppg_mappings), 0)

        t_fc_id2, _ = self._prepare_flow_classifier_test(
            project_id, t_ctx, 'pod_1', 0, src_port_id, True)
        BOTTOM1_FLOWCLASSIFIERS.pop()
        fake_plugin.delete_flow_classifier(q_ctx, t_fc_id2)
        ppg_mappings = db_api.get_bottom_mappings_by_top_id(
            t_ctx, t_fc_id2, constants.RT_FLOW_CLASSIFIER)
        self.assertEqual(len(TOP_FLOWCLASSIFIERS), 0)
        self.assertEqual(len(ppg_mappings), 0)

    @patch.object(sfc_db.SfcDbPlugin, '_validate_pps_in_ppg',
                  new=fake_validate_pps_in_ppg)
    @patch.object(sfc_db.SfcDbPlugin, '_make_port_chain_dict',
                  new=fake_make_port_chain_dict)
    @patch.object(sfc_db.SfcDbPlugin, '_make_port_pair_group_dict',
                  new=fake_make_port_pair_group_dict)
    @patch.object(sfc_db.SfcDbPlugin, '_make_port_pair_dict',
                  new=fake_make_port_pair_dict)
    @patch.object(sfc_db.SfcDbPlugin, 'get_port_chain',
                  new=FakeSfcPlugin.get_port_chain)
    @patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_port',
                  new=FakeCorePlugin.get_port)
    @patch.object(sfc_db.SfcDbPlugin, 'get_port_pairs',
                  new=FakeSfcPlugin.get_port_pairs)
    @patch.object(context, 'get_context_from_neutron_context',
                  new=fake_get_context_from_neutron_context)
    def test_update_service_function_chain(self):
        project_id = TEST_TENANT_ID
        q_ctx = FakeNeutronContext()
        t_ctx = context.get_db_context()
        self._basic_pod_setup()
        fake_sfc_plugin = FakeSfcPlugin()
        fake_fc_plugin = FakeFcPlugin()

        t_net_id = self._prepare_net_test(project_id, t_ctx, 'pod_1')
        src_port_id = self._prepare_port_test(project_id,
                                              t_ctx, 'pod_1', t_net_id)
        ingress1 = self._prepare_port_test(project_id, t_ctx,
                                           'pod_1', t_net_id)
        egress1 = self._prepare_port_test(project_id, t_ctx, 'pod_1', t_net_id)
        t_pp1_id, b_pp1_id = self._prepare_port_pair_test(
            project_id, t_ctx, 'pod_1', 0, ingress1, egress1, True)
        t_ppg1_id, b_ppg1_id = self._prepare_port_pair_group_test(
            project_id, t_ctx, 'pod_1', 0,
            [t_pp1_id], True, [b_pp1_id])
        ppg_mapping = {t_pp1_id: t_ppg1_id}
        self._update_port_pair_test(ppg_mapping, TOP_PORTPAIRS)
        t_fc1_id, b_fc1_id = self._prepare_flow_classifier_test(
            project_id, t_ctx, 'pod_1', 0, src_port_id, True)

        ids = {'t_ppg_id': [t_ppg1_id],
               'b_ppg_id': [b_ppg1_id],
               't_fc_id': [t_fc1_id],
               'b_fc_id': [b_fc1_id]}

        t_pc1_id, b_pc1_id = self._prepare_port_chain_test(
            project_id, t_ctx, 'pod_1', 0, True, ids)
        self._prepare_chain_group_assoc_test(t_pc1_id, t_ppg1_id)
        self._prepare_chain_classifier_assoc_test(t_pc1_id, t_fc1_id)

        pp_body = {'port_pair': {
            'name': 'new_name',
            'description': 'new_pp_description'}}
        fake_sfc_plugin.update_port_pair(q_ctx, t_pp1_id, pp_body)
        self.assertEqual(TOP_PORTPAIRS[0]['description'], 'new_pp_description')
        self.assertEqual(TOP_PORTPAIRS[0]['name'], 'new_name')
        self.assertEqual(BOTTOM1_PORTPAIRS[0]['description'],
                         'new_pp_description')
        self.assertEqual(BOTTOM1_PORTPAIRS[0]['name'], 'new_name')

        fc_body = {'flow_classifier': {
            'name': 'new_name',
            'description': 'new_fc_description'}}
        fake_fc_plugin.update_flow_classifier(q_ctx, t_fc1_id, fc_body)
        self.assertEqual(TOP_FLOWCLASSIFIERS[0]['name'], 'new_name')
        self.assertEqual(TOP_FLOWCLASSIFIERS[0]['description'],
                         'new_fc_description')
        self.assertEqual(BOTTOM1_FLOWCLASSIFIERS[0]['name'], 'new_name')
        self.assertEqual(BOTTOM1_FLOWCLASSIFIERS[0]['description'],
                         'new_fc_description')

        ingress2 = self._prepare_port_test(project_id, t_ctx,
                                           'pod_1', t_net_id)
        egress2 = self._prepare_port_test(project_id, t_ctx, 'pod_1', t_net_id)
        t_pp2_id, b_pp2_id = self._prepare_port_pair_test(
            project_id, t_ctx, 'pod_1', 0, ingress2, egress2, True)
        ppg_body = {'port_pair_group': {
            'name': 'new_name',
            'description': 'new_ppg_description',
            'port_pairs': [t_pp1_id, t_pp2_id]}}
        ppg_mapping = {t_pp2_id: t_ppg1_id}
        self._update_port_pair_test(ppg_mapping, TOP_PORTPAIRS)

        fake_sfc_plugin.update_port_pair_group(q_ctx, t_ppg1_id, ppg_body)
        self.assertEqual(TOP_PORTPAIRGROUPS[0]['name'], 'new_name')
        self.assertEqual(TOP_PORTPAIRGROUPS[0]['description'],
                         'new_ppg_description')
        self.assertEqual(TOP_PORTPAIRGROUPS[0]['port_pairs'],
                         [t_pp1_id, t_pp2_id])
        self.assertEqual(BOTTOM1_PORTPAIRGROUPS[0]['name'], 'new_name')
        self.assertEqual(BOTTOM1_PORTPAIRGROUPS[0]['description'],
                         'new_ppg_description')
        self.assertEqual(BOTTOM1_PORTPAIRGROUPS[0]['port_pairs'],
                         [b_pp1_id, b_pp2_id])
        t_ppg2_id, b_ppg2_id = self._prepare_port_pair_group_test(
            project_id, t_ctx, 'pod_1', 0,
            [], True, [])
        t_fc2_id, b_fc2_id = self._prepare_flow_classifier_test(
            project_id, t_ctx, 'pod_1', 0, src_port_id, True)
        self._prepare_chain_group_assoc_test(t_pc1_id, t_ppg2_id)
        self._prepare_chain_classifier_assoc_test(t_pc1_id, t_fc2_id)
        pc_body = {'port_chain': {
            'name': 'new_name',
            'description': 'new_pc_description',
            'port_pair_groups': [t_ppg1_id, t_ppg2_id],
            'flow_classifiers': [t_fc1_id, t_fc2_id]}}

        fake_sfc_plugin.update_port_chain(q_ctx, t_pc1_id, pc_body)
        self.assertEqual(TOP_PORTCHAINS[0]['name'], 'new_name')
        self.assertEqual(TOP_PORTCHAINS[0]['description'],
                         'new_pc_description')
        self.assertEqual(TOP_PORTCHAINS[0]['port_pair_groups'],
                         [t_ppg1_id, t_ppg2_id])
        self.assertEqual(TOP_PORTCHAINS[0]['flow_classifiers'],
                         [t_fc1_id, t_fc2_id])
        self.assertEqual(BOTTOM1_PORTCHAINS[0]['name'], 'new_name')
        self.assertEqual(BOTTOM1_PORTCHAINS[0]['description'],
                         'new_pc_description')
        self.assertEqual(BOTTOM1_PORTCHAINS[0]['port_pair_groups'],
                         [b_ppg1_id, b_ppg2_id])
        self.assertEqual(BOTTOM1_PORTCHAINS[0]['flow_classifiers'],
                         [b_fc1_id, b_fc2_id])

    def tearDown(self):
        core.ModelBase.metadata.drop_all(core.get_engine())
        test_utils.get_resource_store().clean()
        cfg.CONF.unregister_opts(q_config.core_opts)
        xmanager.IN_TEST = False
