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

from unittest import mock

from mock import call
import webob.exc

from oslo_config import cfg
from vmware_nsx.common import config
from vmware_nsxlib.v3 import exceptions as nsxlib_exc

from gbpservice.neutron.services.grouppolicy.drivers.vmware.nsx_policy import (
    nsx_policy_mapping as driver)
from gbpservice.neutron.tests.unit.services.grouppolicy import (
    test_resource_mapping as test_rmd)


TEST_PROJECT = 'test-project'


class NsxPolicyMappingTestCase(test_rmd.ResourceMappingTestCase):

    def setUp(self):
        self.set_up_mocks()
        self.set_up_config()

        super(NsxPolicyMappingTestCase, self).setUp(
            policy_drivers=['implicit_policy', 'nsx_policy'])
        # REVISIT (annak): currently run with ML2 plugin
        # core_plugin='vmware_nsx.plugin.NsxV3Plugin'

        self.driver = self._gbp_plugin.policy_driver_manager.policy_drivers[
               'nsx_policy'].obj
        self.nsx_policy = self.driver.nsx_policy
        self.nsx_port = self.driver.nsx_port
        self._tenant_id = TEST_PROJECT

    def tearDown(self):
        super(NsxPolicyMappingTestCase, self).tearDown()
        self.patcher.stop()

    def set_up_config(self):
        cfg.CONF.register_opts(driver.policy_opts, driver.DRIVER_OPT_GROUP)
        cfg.CONF.register_opts(config.nsx_v3_opts, group="nsx_v3")
        cfg.CONF.set_override('nsx_policy_manager', '1.1.1.1',
                              driver.DRIVER_OPT_GROUP)
        cfg.CONF.set_override('nsx_api_managers', '1.1.1.1',
                              driver.NSX_V3_GROUP)

    def set_up_mocks(self):
        mock.patch("vmware_nsxlib.v3.client.NSX3Client").start()
        mock.patch("vmware_nsxlib.v3.policy_resources"
                   ".NsxPolicyEnforcementPointApi").start()
        mock.patch("vmware_nsxlib.v3.cluster.ClusteredAPI"
                   "._init_endpoints").start()
        # To avoid:
        # '>' not supported between instances of 'MagicMock' and 'int'
        # due to
        # `MAX_NSGROUPS_CRITERIA_TAGS = max(10, max_tags - 5)`
        # in vmware_nsxlib.v3.utils._update_max_nsgroups_criteria_tags()
        self.patcher = mock.patch("vmware_nsxlib.v3.utils."
            "_update_max_nsgroups_criteria_tags")
        self.patcher.return_value = 10
        self.patcher.start()

    def _mock_domain_create(self):
        return mock.patch.object(self.nsx_policy.domain, 'create_or_overwrite')

    def _mock_domain_delete(self):
        return mock.patch.object(self.nsx_policy.domain, 'delete')

    def _mock_service_create(self):
        return mock.patch.object(self.nsx_policy.service,
                                 'create_or_overwrite')

    def _mock_icmp_service_create(self):
        return mock.patch.object(self.nsx_policy.icmp_service,
                                 'create_or_overwrite')

    def _mock_service_delete(self):
        return mock.patch.object(self.nsx_policy.service, 'delete')

    def _mock_icmp_service_delete(self):
        return mock.patch.object(self.nsx_policy.icmp_service, 'delete')

    def _mock_group_create(self):
        return mock.patch.object(self.nsx_policy.group, 'create_or_overwrite')

    def _mock_group_create_fails(self):
        return mock.patch.object(self.nsx_policy.group, 'create_or_overwrite',
                                 side_effect=nsxlib_exc.ManagerError)

    def _mock_group_delete(self):
        return mock.patch.object(self.nsx_policy.group, 'delete')

    def _mock_map_create(self):
        return mock.patch.object(self.nsx_policy.comm_map,
                                 'create_or_overwrite')

    def _mock_map_delete(self):
        return mock.patch.object(self.nsx_policy.comm_map, 'delete')

    def _mock_map_create_fails(self):
        return mock.patch.object(self.nsx_policy.comm_map,
                                 'create_or_overwrite',
                                 side_effect=nsxlib_exc.ManagerError)

    def _mock_nth_map_create_fails(self, n=2):
        self.call_count = 1

        def raise_on_nth_call(**kwargs):
            if self.call_count == n:
                raise nsxlib_exc.ManagerError
            else:
                self.call_count += 1
        return mock.patch.object(self.nsx_policy.comm_map,
                                 'create_or_overwrite',
                                 side_effect=raise_on_nth_call)

    def _mock_policy_create_fails(self):
        return mock.patch.object(self.policy_api, 'create_or_overwrite',
                                 side_effect=nsxlib_exc.ManagerError)

    def _mock_policy_delete(self):
        return mock.patch.object(self.policy_api, 'delete')

    def _mock_nsx_db(self):
        def mirror_port_id(session, port_id):
            return None, port_id
        mock.patch('vmware_nsx.db.db.get_nsx_switch_and_port_id',
                   side_effect=mirror_port_id).start()

    def _mock_nsx_port_update(self):
        return mock.patch.object(self.nsx_port, 'update')


class TestPolicyClassifier(NsxPolicyMappingTestCase):

    def test_l4_lifecycle(self):
        with self._mock_service_create() as service_create_call, \
            self._mock_service_delete() as service_delete_call:

            # classifier create
            cl = self.create_policy_classifier(
                name='test',
                protocol='TCP',
                port_range='80',
                direction='bi')['policy_classifier']

            # verify API call to create the service
            service_create_call.assert_called_with(
                name=mock.ANY,
                description=mock.ANY,
                protocol='tcp',
                dest_ports=['80'],
                service_id=mock.ANY)

            service_create_call.reset_mock()

            # classifier update
            cl = self.update_policy_classifier(
                    cl['id'],
                    port_range='443')['policy_classifier']

            service_create_call.assert_called_with(
                name=mock.ANY,
                description=mock.ANY,
                protocol='tcp',
                dest_ports=['443'],
                service_id=mock.ANY)

            # classifier delete
            self.delete_policy_classifier(cl['id'])

            service_delete_call.assert_called_with(cl['id'])

    def test_create_port_range(self):
        with self._mock_service_create() as service_create_call:

            self.create_policy_classifier(name='test',
                                          protocol='UDP',
                                          port_range='777:888',
                                          direction='in')

            port_list = [str(p) for p in range(777, 889)]
            service_create_call.assert_called_with(
                name=mock.ANY,
                description=mock.ANY,
                protocol='udp',
                dest_ports=port_list,
                service_id=mock.ANY)

    def test_create_without_ports(self):
        with self._mock_service_create() as service_create_call:

            self.create_policy_classifier(name='test',
                                          protocol='TCP',
                                          direction='in')

            service_create_call.assert_called_with(
                name=mock.ANY,
                description=mock.ANY,
                protocol='tcp',
                dest_ports=[],
                service_id=mock.ANY)

    def test_icmp_lifecycle(self):
        with self._mock_icmp_service_create() as service_create_call, \
            self._mock_icmp_service_delete() as service_delete_call:

            cl = self.create_policy_classifier(
                name='test',
                protocol='icmp',
                direction='bi')['policy_classifier']

            # verify API call to create the service
            service_create_call.assert_called()

            self.delete_policy_classifier(cl['id'])

            service_delete_call.assert_called_with(cl['id'])

    def test_update_protocol_fails(self):
        with self._mock_icmp_service_create():

            cl = self.create_policy_classifier(
                name='test',
                protocol='icmp',
                direction='bi')['policy_classifier']

            self.assertRaises(webob.exc.HTTPClientError,
                              self.update_policy_classifier,
                              cl['id'],
                              protocol='tcp',
                              dest_ports=['80'])

    def test_icmpv6_protocol_fails(self):
        self.assertRaises(webob.exc.HTTPClientError,
                          self.create_policy_classifier,
                          name='test',
                          protocol='58',
                          direction='bi')


class TestPolicyTargetGroup(NsxPolicyMappingTestCase):

    def _prepare_rule_set(self, name='test_rule',
                          direction='bi', rule_count=1):
        self.rules = []
        with self._mock_service_create():

            for i in range(rule_count):
                self.rules.append(
                    self._create_simple_policy_rule(direction=direction))

            rule_ids = [rule['id'] for rule in self.rules]
            return self.create_policy_rule_set(name=name,
                policy_rules=rule_ids)['policy_rule_set']

    def assert_neutron_resources(self, net_count, subnet_count, port_count):
        networks = self._plugin.get_networks(self._context)
        self.assertEqual(net_count, len(networks))

        subnets = self._plugin.get_subnets(self._context)
        self.assertEqual(subnet_count, len(subnets))

        ports = self._plugin.get_ports(self._context)
        self.assertEqual(port_count, len(ports))

    def assert_neutron_rollback(self):
        self.assert_neutron_resources(0, 0, 0)

    def group_call(self, name, group_id):
        return call(domain_id=TEST_PROJECT,
                    name=driver.generate_nsx_name(group_id, name),
                    description=mock.ANY,
                    cond_val=group_id,
                    group_id=group_id)

    def ingress_map_call(self, prs_id, provider_ids,
                         consumer_ids, service_ids):
        return call(domain_id=TEST_PROJECT,
                    map_id=mock.ANY,
                    name=driver.append_in_dir(prs_id),
                    service_ids=service_ids,
                    description=mock.ANY,
                    source_groups=consumer_ids,
                    dest_groups=provider_ids,
                    )

    def egress_map_call(self, prs_id, provider_ids,
                        consumer_ids, service_ids):
        return call(domain_id=TEST_PROJECT,
                    map_id=mock.ANY,
                    service_ids=service_ids,
                    name=driver.append_out_dir(prs_id),
                    description=mock.ANY,
                    source_groups=provider_ids,
                    dest_groups=consumer_ids)

    def test_create_first_ptg_for_project(self):
        '''Create first ptg for tenant and verify domain creation'''

        with self._mock_domain_create() as domain_create,\
            self._mock_group_create() as group_create,\
            self._mock_map_create() as map_create:

            ptg = self.create_policy_target_group(
                name='test')['policy_target_group']

            domain_create.assert_called_with(domain_id=TEST_PROJECT,
                                             name=mock.ANY,
                                             description=mock.ANY)
            group_create.assert_has_calls([self.group_call('test', ptg['id'])])
            map_create.assert_not_called()

    def _test_ptg_pair_with_rule_set(self,
                                     direction_in=True,
                                     direction_out=True,
                                     rule_count=1):
        '''Test consumer and producer group pair with single rule lifecycle.

        Verify backend group and rule creation calls.
        Verify spawned neutron resources.
        '''

        direction = 'bi'
        if not direction_in:
            direction = 'out'
        if not direction_out:
            direction = 'in'

        policy_rule_set = self._prepare_rule_set(direction=direction,
                                                 rule_count=rule_count)

        # Create group pair
        with self._mock_group_create() as group_create,\
            self._mock_map_create() as map_create,\
            self._mock_domain_create():

            provider_ptg, consumer_ptg = self._create_provider_consumer_ptgs(
                    policy_rule_set['id'])

            # validate group creation on backend
            calls = [self.group_call('ptg1', provider_ptg),
                     self.group_call('ptg2', consumer_ptg)]
            group_create.assert_has_calls(calls)

            # validate communication map creation on backend
            calls = []
            classifier_ids = sorted([rule['policy_classifier_id']
                                     for rule in self.rules])
            if direction_in:
                calls.append(self.ingress_map_call(
                    policy_rule_set['id'],
                    [provider_ptg],
                    [consumer_ptg],
                    classifier_ids))
            if direction_out:
                calls.append(self.egress_map_call(
                    policy_rule_set['id'],
                    [provider_ptg],
                    [consumer_ptg],
                    classifier_ids))
            map_create.assert_has_calls(calls)

            # validate neutron resources
            self.assert_neutron_resources(2, 2, 2)

        # Delete producer
        with self._mock_map_delete() as map_delete,\
            self._mock_group_delete() as group_delete,\
            self._mock_domain_delete() as domain_delete:

            self.delete_policy_target_group(provider_ptg)

            # verify communication map delete on backend
            calls = []
            if direction_in:
                calls.append(call(
                    TEST_PROJECT,
                    driver.append_in_dir(policy_rule_set['id'])))
            if direction_out:
                calls.append(call(
                    TEST_PROJECT,
                    driver.append_out_dir(policy_rule_set['id'])))

            map_delete.assert_has_calls(calls)

            # verify group delete call
            group_delete.assert_called_with(TEST_PROJECT, provider_ptg)

            # verify domain not deleted yet
            domain_delete.assert_not_called()

        # Delete consumer
        with self._mock_map_delete() as map_delete,\
            self._mock_group_delete() as group_delete,\
            self._mock_domain_delete() as domain_delete:

            self.delete_policy_target_group(consumer_ptg)

            # no deletions on communication map are expected
            map_delete.assert_not_called()

            # verify group delete call
            group_delete.assert_called_with(TEST_PROJECT, consumer_ptg)

            # last group is deleted, domain should go as well
            domain_delete.assert_called_with(TEST_PROJECT)

    def test_ptg_pair_with_single_rule_in(self):
        self._test_ptg_pair_with_rule_set(True, False)

    def test_ptg_pair_with_single_rule_out(self):
        self._test_ptg_pair_with_rule_set(False, True)

    def test_ptg_pair_with_single_rule_bi(self):
        self._test_ptg_pair_with_rule_set(True, True)

    def test_ptg_pair_with_multi_rule_in(self):
        self._test_ptg_pair_with_rule_set(True, False, 2)

    def test_ptg_pair_with_multi_rule_out(self):
        self._test_ptg_pair_with_rule_set(False, True, 3)

    def test_ptg_pair_with_multi_rule_bi(self):
        self._test_ptg_pair_with_rule_set(True, True, 5)

    def test_create_fail_isolated(self):
        '''Verify integrity when backend fails on isolated group creation.

        Verify backend receives a group delete call.
        Verify spawned neutron resources are cleaned up.
        '''

        policy_rule_set = self._prepare_rule_set()

        with self._mock_domain_create(),\
            self._mock_group_create_fails(),\
            self._mock_group_delete() as group_delete,\
            self._mock_domain_delete() as domain_delete:

            self.assertRaises(webob.exc.HTTPClientError,
                              self._create_provider_consumer_ptgs,
                              policy_rule_set['id'])

            group_delete.assert_called_with(self._tenant_id,
                                            mock.ANY)

            # verify domain deletion since group failed to create
            domain_delete.assert_called_with(TEST_PROJECT)

            self.assert_neutron_rollback()

    def test_create_fail_connected(self):
        '''Verify integrity when backend fails on connectivity map creation

        This test creates a pair of groups. First group creation succeeds,
        while second fails on connectivity enforcement.
        Verify backend receives a group delete call for second group.
        Verify spawned neutron resources are cleaned up for second group.
        '''

        policy_rule_set = self._prepare_rule_set()

        with self._mock_group_create(),\
            self._mock_map_create_fails(),\
            self._mock_group_delete() as group_delete:

            self.assertRaises(webob.exc.HTTPClientError,
                              self._create_provider_consumer_ptgs,
                              policy_rule_set['id'])

            group_delete.assert_called_with(self._tenant_id, mock.ANY)

            self.assert_neutron_resources(1, 1, 1)

    def test_create_fail_multi_connected(self):
        '''Verify integrity when backend fails on connectivity map creation

        This test creates three groups a<-->b<==>c
        B is created last, and creation fails on its last connectivity
        enforcement.
        Verify all maps are deleted in cleanup.
        Verify spawned neutron resources are cleaned up for third group.
        '''

        prs1 = self._prepare_rule_set()['id']
        service1 = self.rules[0]['policy_classifier_id']
        prs2 = self._prepare_rule_set()['id']
        service2 = self.rules[0]['policy_classifier_id']
        prs3 = self._prepare_rule_set()['id']
        service3 = self.rules[0]['policy_classifier_id']

        # Create a and c
        with self._mock_group_create(),\
            self._mock_map_create():

            ab_dict = {prs1: None}
            bc_dict = {prs2: None, prs3: None}
            a = self.create_policy_target_group(
                name='a',
                provided_policy_rule_sets=ab_dict)['policy_target_group']['id']
            c = self.create_policy_target_group(
                name='c',
                consumed_policy_rule_sets=bc_dict)['policy_target_group']['id']

        with self._mock_group_create(),\
            self._mock_nth_map_create_fails(n=6) as map_create,\
            self._mock_map_delete() as map_delete,\
            self._mock_group_delete() as group_delete:

            self.assertRaises(webob.exc.HTTPClientError,
                              self.create_policy_target_group,
                              name='c',
                              consumed_policy_rule_sets=ab_dict,
                              provided_policy_rule_sets=bc_dict)

            b = mock.ANY
            map_create_calls = [
                self.ingress_map_call(prs1, [a], [b], [service1]),
                self.egress_map_call(prs1, [a], [b], [service1]),
                self.ingress_map_call(prs2, [b], [c], [service2]),
                self.egress_map_call(prs2, [b], [c], [service2]),
                self.ingress_map_call(prs3, [b], [c], [service3]),
                self.egress_map_call(prs3, [b], [c], [service3])]

            map_create.assert_has_calls(map_create_calls, any_order=True)

            map_delete_calls = [
                call(TEST_PROJECT, driver.append_in_dir(prs1)),
                call(TEST_PROJECT, driver.append_out_dir(prs1)),
                call(TEST_PROJECT, driver.append_in_dir(prs2)),
                call(TEST_PROJECT, driver.append_out_dir(prs2)),
                call(TEST_PROJECT, driver.append_in_dir(prs3))]

            map_delete.assert_has_calls(map_delete_calls, any_order=True)

            group_delete.assert_called_with(TEST_PROJECT, mock.ANY)

            self.assert_neutron_resources(2, 2, 2)

    def test_create_ptg_pair_multi_rule_set(self):
        '''Create ptg pair based on 3 rule sets

        First rule set is simulated to have only ingress connectivity,
        second - only egress connectivity, and third - both
        '''
        prs1 = self._prepare_rule_set()['id']
        service1 = self.rules[0]['policy_classifier_id']
        prs2 = self._prepare_rule_set()['id']
        service2 = self.rules[0]['policy_classifier_id']
        prs3 = self._prepare_rule_set()['id']
        service3 = self.rules[0]['policy_classifier_id']

        with self._mock_domain_create(),\
            self._mock_group_create() as group_create,\
            self._mock_map_create() as map_create:

            rule_set_dict = {prs1: None, prs2: None, prs3: None}
            provider_ptg = self.create_policy_target_group(
                name='ptg1', provided_policy_rule_sets=rule_set_dict)
            provider_id = provider_ptg['policy_target_group']['id']
            consumer_ptg = self.create_policy_target_group(
                name='ptg2', consumed_policy_rule_sets=rule_set_dict)
            consumer_id = consumer_ptg['policy_target_group']['id']

            group_create.assert_has_calls(
                [self.group_call('ptg1', provider_id),
                 self.group_call('ptg2', consumer_id)])

            map_calls = [
                self.ingress_map_call(prs1, [provider_id],
                                      [consumer_id], [service1]),
                self.egress_map_call(prs2, [provider_id],
                                     [consumer_id], [service2]),
                self.ingress_map_call(prs3, [provider_id],
                                      [consumer_id], [service3]),
                self.egress_map_call(prs3, [provider_id],
                                     [consumer_id], [service3])]
            map_create.assert_has_calls(map_calls, any_order=True)

    def test_create_ptg_ring(self):
        ring_size = 10

        prs_ids = []
        services = []
        for i in range(0, ring_size):
            prs_ids.append(self._prepare_rule_set()['id'])
            services.append(self.rules[0]['policy_classifier_id'])

        # Create ring topology
        with self._mock_domain_create(),\
            self._mock_group_create() as group_create,\
            self._mock_map_create() as map_create:

            group_calls = []
            map_calls = []
            ptg_ids = []
            for i in range(0, ring_size):
                provided_rule_set_dict = {prs_ids[i]: None}
                next_i = (i + 1) % ring_size
                consumed_rule_set_dict = {prs_ids[next_i]: None}
                name = 'ptg_%d' % i
                ptg = self.create_policy_target_group(
                        name=name,
                        provided_policy_rule_sets=provided_rule_set_dict,
                        consumed_policy_rule_sets=consumed_rule_set_dict)

                ptg_id = ptg['policy_target_group']['id']
                ptg_ids.append(ptg_id)
                group_calls.append(self.group_call(name, ptg_id))

                if i > 0:
                    map_calls.append(self.ingress_map_call(
                        prs_ids[i],
                        [ptg_id],
                        [ptg_ids[i - 1]],
                        [services[i]]))

            map_calls.append(self.ingress_map_call(prs_ids[0],
                                                   [ptg_ids[0]],
                                                   [ptg_id],
                                                   [services[0]]))

            group_create.assert_has_calls(group_calls)
            map_create.assert_has_calls(map_calls, any_order=True)

            self.assert_neutron_resources(ring_size, ring_size, ring_size)

        # Delete single group and verify connectors are deleted
        with self._mock_map_delete() as map_delete,\
            self._mock_map_create() as map_create,\
            self._mock_group_delete() as group_delete:

            ptg_id = ptg_ids[2]
            self.delete_policy_target_group(ptg_id)

            map_calls = [call(TEST_PROJECT, driver.append_in_dir(prs_ids[2])),
                         call(TEST_PROJECT, driver.append_out_dir(prs_ids[2])),
                         call(TEST_PROJECT, driver.append_in_dir(prs_ids[3])),
                         call(TEST_PROJECT, driver.append_out_dir(prs_ids[3]))]

            map_delete.assert_has_calls(map_calls)
            map_create.assert_not_called()
            group_delete.assert_called_with(TEST_PROJECT, ptg_id)

        # Remove connectors from single group
        with self._mock_map_delete() as map_delete,\
            self._mock_map_create() as map_create,\
            self._mock_group_delete() as group_delete:

            ptg_id = ptg_ids[5]
            self.update_policy_target_group(
                ptg_id, provided_policy_rule_sets={})

            map_calls = [call(TEST_PROJECT, driver.append_in_dir(prs_ids[5]))]
            map_delete.assert_has_calls(map_calls)
            map_create.assert_not_called()
            group_delete.assert_not_called()

    def test_create_ptg_star(self):
        '''Star-like topology (single producer and N consumers) lifecycle'''

        star_size = 10
        policy_rule_set = self._prepare_rule_set()
        service = self.rules[0]['policy_classifier_id']
        prs_id = policy_rule_set['id']

        # Create topology
        with self._mock_domain_create(),\
            self._mock_group_create() as group_create,\
            self._mock_map_create() as map_create:

            policy_rule_set_dict = {prs_id: None}
            provider_ptg = self.create_policy_target_group(
                    name='producer',
                    provided_policy_rule_sets=policy_rule_set_dict)
            provider_id = provider_ptg['policy_target_group']['id']

            group_calls = [self.group_call('producer', provider_id)]
            map_calls = []

            consumer_ids = []
            for i in range(0, star_size):
                name = 'consumer_%d' % i
                consumer_ptg = self.create_policy_target_group(
                        name=name,
                        consumed_policy_rule_sets=policy_rule_set_dict)

                consumer_id = consumer_ptg['policy_target_group']['id']
                consumer_ids.append(consumer_id)

                group_calls.append(self.group_call(name, consumer_id))

                map_calls.append(self.ingress_map_call(
                    prs_id,
                    [provider_id],
                    consumer_ids[:],
                    [service]))

                map_calls.append(self.egress_map_call(
                    prs_id,
                    [provider_id],
                    consumer_ids[:],
                    [service]))

            group_create.assert_has_calls(group_calls)
            map_create.assert_has_calls(map_calls)

            star_size += 1
            self.assert_neutron_resources(star_size, star_size, star_size)

        # Delete one consumer group
        with self._mock_map_delete() as map_delete,\
            self._mock_map_create() as map_create,\
            self._mock_group_delete() as group_delete:

            consumer_id = consumer_ids.pop(0)
            self.delete_policy_target_group(consumer_id)

            map_create.assert_has_calls(
                [self.ingress_map_call(prs_id,
                                      [provider_id],
                                      consumer_ids,
                                      [service])])

            map_delete.assert_not_called()

            group_delete.assert_called_with(TEST_PROJECT, consumer_id)

            star_size -= 1
            self.assert_neutron_resources(star_size, star_size, star_size)

        # Delete provider group
        with self._mock_map_delete() as map_delete,\
            self._mock_map_create() as map_create,\
            self._mock_group_delete() as group_delete:

            self.delete_policy_target_group(provider_id)

            map_create.assert_not_called()
            map_delete.assert_has_calls(
                [call(TEST_PROJECT, driver.append_in_dir(prs_id)),
                 call(TEST_PROJECT, driver.append_out_dir(prs_id))])

            star_size -= 1
            group_delete.assert_called_with(TEST_PROJECT, provider_id)

    def test_update_rule_set(self):
        '''Under consumer and producer group pair with single rule lifecycle,
        Update rule set and verify backend gets updated
        '''
        rule_set = self._prepare_rule_set(direction='in')

        # Create group pair
        with self._mock_group_create(), \
            self._mock_map_create(), \
            self._mock_domain_create():

            provider_ptg, consumer_ptg = self._create_provider_consumer_ptgs(
                    rule_set['id'])

        # Add another rule to the rule set
        with self._mock_service_create(),\
            self._mock_map_create() as map_update:

            rule = self._create_simple_policy_rule(direction='in')
            self.rules.append(rule)

            rules = [rule['id'] for rule in self.rules]
            self.update_policy_rule_set(rule_set['id'], policy_rules=rules)
            classifier_ids = sorted([rule['policy_classifier_id']
                                     for rule in self.rules])

            map_update.assert_has_calls([self.ingress_map_call(
                rule_set['id'],
                [provider_ptg],
                [consumer_ptg],
                classifier_ids)])

        # Delete all rules from rule set, leaving the rule set
        with self._mock_map_delete() as map_delete:
            self.update_policy_rule_set(rule_set['id'], policy_rules=[])

            # verify communication map delete on backend
            map_delete.assert_any_call(
                TEST_PROJECT, driver.append_in_dir(rule_set['id']))

    def test_update_rule_classifier(self):
        '''Under consumer and producer group pair with single rule lifecycle,
        Update rule set and verify backend gets updated
        '''
        rule_set = self._prepare_rule_set(direction='in')

        # Create group pair
        with self._mock_group_create(), \
            self._mock_map_create(), \
            self._mock_domain_create():

            provider_ptg, consumer_ptg = self._create_provider_consumer_ptgs(
                    rule_set['id'])

        # Update classifier in rule
        with self._mock_service_create(),\
            self._mock_map_create() as map_update:

            cl = self.create_policy_classifier(
                name='test',
                protocol='UDP',
                port_range='512',
                direction='bi')['policy_classifier']

            self.update_policy_rule(self.rules[0]['id'],
                                    policy_classifier_id=cl['id'])

            map_update.assert_has_calls([self.ingress_map_call(
                rule_set['id'],
                [provider_ptg],
                [consumer_ptg],
                [cl['id']])])


class TestPolicyRuleSet(NsxPolicyMappingTestCase):

    def test_basic(self):
        ''' Create and delete unused rule set'''

        rule = self._create_simple_policy_rule()
        rule_set = self.create_policy_rule_set(name='test',
            policy_rules=[rule['id']])['policy_rule_set']

        self.update_policy_rule_set(rule_set['id'], name='test1')
        self.delete_policy_rule_set(rule_set['id'])


class TestPolicyTargetTag(NsxPolicyMappingTestCase):

    def _prepare_group(self, name='test'):
        with self._mock_group_create():
            return self.create_policy_target_group(
                name='test')['policy_target_group']

    def test_target_lifecycle(self):
        self._mock_nsx_db()

        ptg = self._prepare_group()

        # create policy target and verify port tag update
        with self._mock_nsx_port_update() as port_update:

            target = self.create_policy_target(
                policy_target_group_id=ptg['id'])['policy_target']

            # nsx mock function will map neutron port id to same value
            # for nsx port id
            port_update.assert_called_once_with(
                target['port_id'],
                None,
                tags_update=[{'scope': 'gbp',
                              'tag': ptg['id']}])

        # verify group membership change is not supported
        ptg1 = self._prepare_group()
        self.assertRaises(webob.exc.HTTPClientError,
                          self.update_policy_target,
                          target['id'],
                          policy_target_group_id=ptg1['id'])

        # policy target deletion should not affect backend policy-wise
        self.delete_policy_target(target['id'])


class TestL3Policy(NsxPolicyMappingTestCase):

    def test_ipv6_supported(self):
        self.assertRaises(webob.exc.HTTPClientError,
                          self.create_l3_policy,
                          ip_version=6,
                          ip_pool='1001::0/64')
