# Copyright 2015 VMware, Inc.
# 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

import eventlet
import mock

from oslo_serialization import jsonutils
from oslo_utils import uuidutils

from vmware_nsxlib.tests.unit.v3 import mocks
from vmware_nsxlib.tests.unit.v3 import nsxlib_testcase
from vmware_nsxlib.tests.unit.v3 import test_client
from vmware_nsxlib.tests.unit.v3 import test_constants
from vmware_nsxlib.v3 import core_resources
from vmware_nsxlib.v3 import exceptions
from vmware_nsxlib.v3 import nsx_constants
from vmware_nsxlib.v3 import resources
from vmware_nsxlib.v3 import utils


class BaseTestResource(nsxlib_testcase.NsxClientTestCase):
    """Base class for resources tests

    Contains tests for the simple get/list/delete apis
    and an api to get the mocked resource
    """
    def setUp(self, resource=None):
        self.resource = resource
        super(BaseTestResource, self).setUp()

    def get_mocked_resource(self, mock_validate=True, response=None,
                            response_repeat=1):
        session_response = None
        if response:
            session_response = mocks.MockRequestsResponse(
                200, jsonutils.dumps(response))
            if response_repeat > 1:
                session_response = [session_response] * response_repeat

        return self.mocked_resource(
            self.resource, mock_validate=mock_validate,
            session_response=session_response)

    def test_get_resource(self):
        if not self.resource:
            return
        mocked_resource = self.get_mocked_resource()
        fake_uuid = uuidutils.generate_uuid()
        mocked_resource.get(fake_uuid)
        test_client.assert_json_call(
            'get', mocked_resource,
            'https://1.2.3.4/api/v1/%s/%s' % (mocked_resource.uri_segment,
                                              fake_uuid),
            headers=self.default_headers())

    def test_list_all(self):
        if not self.resource:
            return
        mocked_resource = self.get_mocked_resource()
        mocked_resource.list()
        test_client.assert_json_call(
            'get', mocked_resource,
            'https://1.2.3.4/api/v1/%s' % mocked_resource.uri_segment,
            headers=self.default_headers())

    def test_delete_resource(self, extra_params=None):
        if not self.resource:
            return
        mocked_resource = self.get_mocked_resource()
        fake_uuid = uuidutils.generate_uuid()
        mocked_resource.delete(fake_uuid)
        uri = 'https://1.2.3.4/api/v1/%s/%s' % (mocked_resource.uri_segment,
                                                fake_uuid)
        if extra_params:
            uri = uri + '?' + extra_params
        test_client.assert_json_call(
            'delete', mocked_resource, uri,
            headers=self.default_headers())


class TestSwitchingProfileTestCase(BaseTestResource):

    def setUp(self):
        self.types = resources.SwitchingProfileTypes
        super(TestSwitchingProfileTestCase, self).setUp(
            resources.SwitchingProfile)

    def test_switching_profile_create(self):
        mocked_resource = self.get_mocked_resource()

        mocked_resource.create(self.types.PORT_MIRRORING,
                               'pm-profile', 'port mirror prof')

        test_client.assert_json_call(
            'post', mocked_resource,
            'https://1.2.3.4/api/v1/switching-profiles',
            data=jsonutils.dumps({
                'resource_type': self.types.PORT_MIRRORING,
                'display_name': 'pm-profile',
                'description': 'port mirror prof'
            }, sort_keys=True),
            headers=self.default_headers())

    def test_switching_profile_update(self):

        tags = [
            {
                'scope': 'os-project-id',
                'tag': 'project-1'
            },
            {
                'scope': 'os-api-version',
                'tag': '2.1.1.0'
            }
        ]

        mocked_resource = self.get_mocked_resource()
        fake_uuid = uuidutils.generate_uuid()

        mocked_resource.update(
            fake_uuid, self.types.PORT_MIRRORING, tags=tags)

        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/switching-profiles/%s' % fake_uuid,
            data=jsonutils.dumps({
                'resource_type': self.types.PORT_MIRRORING,
                'tags': tags
            }, sort_keys=True),
            headers=self.default_headers())

    def test_spoofgaurd_profile_create(self):

        tags = [
            {
                'scope': 'os-project-id',
                'tag': 'project-1'
            },
            {
                'scope': 'os-api-version',
                'tag': '2.1.1.0'
            }
        ]

        mocked_resource = self.get_mocked_resource()

        mocked_resource.create_spoofguard_profile(
            'plugin-spoof', 'spoofguard-for-plugin',
            whitelist_ports=True, tags=tags)

        test_client.assert_json_call(
            'post', mocked_resource,
            'https://1.2.3.4/api/v1/switching-profiles',
            data=jsonutils.dumps({
                'resource_type': self.types.SPOOF_GUARD,
                'display_name': 'plugin-spoof',
                'description': 'spoofguard-for-plugin',
                'white_list_providers': ['LPORT_BINDINGS'],
                'tags': tags
            }, sort_keys=True),
            headers=self.default_headers())

    def test_create_dhcp_profile(self):

        tags = [
            {
                'scope': 'os-project-id',
                'tag': 'project-1'
            },
            {
                'scope': 'os-api-version',
                'tag': '2.1.1.0'
            }
        ]

        mocked_resource = self.get_mocked_resource()

        mocked_resource.create_dhcp_profile(
            'plugin-dhcp', 'dhcp-for-plugin',
            tags=tags)

        test_client.assert_json_call(
            'post', mocked_resource,
            'https://1.2.3.4/api/v1/switching-profiles',
            data=jsonutils.dumps({
                'bpdu_filter': {
                    'enabled': True,
                    'white_list': []
                },
                'resource_type': self.types.SWITCH_SECURITY,
                'display_name': 'plugin-dhcp',
                'description': 'dhcp-for-plugin',
                'tags': tags,
                'dhcp_filter': {
                    'client_block_enabled': True,
                    'server_block_enabled': False
                },
                'rate_limits': {
                    'enabled': False,
                    'rx_broadcast': 0,
                    'tx_broadcast': 0,
                    'rx_multicast': 0,
                    'tx_multicast': 0
                },
                'block_non_ip_traffic': True
            }, sort_keys=True),
            headers=self.default_headers())

    def test_create_mac_learning_profile(self):

        tags = [
            {
                'scope': 'os-project-id',
                'tag': 'project-1'
            },
            {
                'scope': 'os-api-version',
                'tag': '2.1.1.0'
            }
        ]

        mocked_resource = self.get_mocked_resource()

        mocked_resource.create_mac_learning_profile(
            'plugin-mac-learning', 'mac-learning-for-plugin',
            tags=tags)

        test_client.assert_json_call(
            'post', mocked_resource,
            'https://1.2.3.4/api/v1/switching-profiles',
            data=jsonutils.dumps({
                'mac_learning': {
                    'enabled': True,
                },
                'resource_type': self.types.MAC_LEARNING,
                'display_name': 'plugin-mac-learning',
                'description': 'mac-learning-for-plugin',
                'tags': tags,
                'mac_change_allowed': True,
            }, sort_keys=True),
            headers=self.default_headers())

    def test_find_by_display_name(self):
        resp_resources = {
            'results': [
                {'display_name': 'resource-1'},
                {'display_name': 'resource-2'},
                {'display_name': 'resource-3'}
            ]
        }
        mocked_resource = self.get_mocked_resource(response=resp_resources,
                                                   response_repeat=3)

        self.assertEqual([{'display_name': 'resource-1'}],
                         mocked_resource.find_by_display_name('resource-1'))
        self.assertEqual([{'display_name': 'resource-2'}],
                         mocked_resource.find_by_display_name('resource-2'))
        self.assertEqual([{'display_name': 'resource-3'}],
                         mocked_resource.find_by_display_name('resource-3'))

        resp_resources = {
            'results': [
                {'display_name': 'resource-1'},
                {'display_name': 'resource-1'},
                {'display_name': 'resource-1'}
            ]
        }
        mocked_resource = self.get_mocked_resource(response=resp_resources)
        self.assertEqual(resp_resources['results'],
                         mocked_resource.find_by_display_name('resource-1'))

    def test_list_all(self):
        mocked_resource = self.get_mocked_resource()
        mocked_resource.list()
        test_client.assert_json_call(
            'get', mocked_resource,
            'https://1.2.3.4/api/v1/switching-profiles/'
            '?include_system_owned=True',
            data=None,
            headers=self.default_headers())


class LogicalPortTestCase(BaseTestResource):

    def setUp(self):
        super(LogicalPortTestCase, self).setUp(resources.LogicalPort)

    def _get_profile_dicts(self, fake_port):
        fake_profile_dicts = []
        for profile_id in fake_port['switching_profile_ids']:
            fake_profile_dicts.append({'resource_type': profile_id['key'],
                                       'id': profile_id['value']})
        return fake_profile_dicts

    def _get_pktcls_bindings(self):
        fake_pkt_classifiers = []
        fake_binding_repr = []
        for i in range(0, 3):
            ip = "9.10.11.%s" % i
            mac = "00:0c:29:35:4a:%sc" % i
            fake_pkt_classifiers.append(resources.PacketAddressClassifier(
                ip, mac, None))
            fake_binding_repr.append({
                'ip_address': ip,
                'mac_address': mac
            })
        return fake_pkt_classifiers, fake_binding_repr

    def test_create_logical_port(self):
        """Test creating a port.

        returns the correct response and 200 status
        """
        fake_port = test_constants.FAKE_PORT.copy()

        profile_dicts = self._get_profile_dicts(fake_port)

        pkt_classifiers, binding_repr = self._get_pktcls_bindings()

        mocked_resource = self.get_mocked_resource()
        description = 'dummy'
        switch_profile = resources.SwitchingProfile
        mocked_resource.create(
            fake_port['logical_switch_id'],
            fake_port['attachment']['id'],
            address_bindings=pkt_classifiers,
            switch_profile_ids=switch_profile.build_switch_profile_ids(
                mock.Mock(), *profile_dicts),
            description=description)

        resp_body = {
            'logical_switch_id': fake_port['logical_switch_id'],
            'switching_profile_ids': fake_port['switching_profile_ids'],
            'attachment': {
                'attachment_type': 'VIF',
                'id': fake_port['attachment']['id']
            },
            'admin_state': 'UP',
            'address_bindings': binding_repr,
            'description': description
        }

        test_client.assert_json_call(
            'post', mocked_resource,
            'https://1.2.3.4/api/v1/logical-ports',
            data=jsonutils.dumps(resp_body, sort_keys=True),
            headers=self.default_headers())

    def test_create_logical_port_with_attachtype_cif(self):
        """Test creating a port returns the correct response and 200 status

        """
        fake_port = copy.deepcopy(test_constants.FAKE_CONTAINER_PORT)

        profile_dicts = self._get_profile_dicts(fake_port)

        pkt_classifiers, binding_repr = self._get_pktcls_bindings()

        fake_port['address_bindings'] = binding_repr

        mocked_resource = self.get_mocked_resource()
        switch_profile = resources.SwitchingProfile
        fake_port_ctx = fake_port['attachment']['context']

        fake_container_host_vif_id = fake_port_ctx['container_host_vif_id']

        mocked_resource.create(
            fake_port['logical_switch_id'],
            fake_port['attachment']['id'],
            parent_vif_id=fake_container_host_vif_id,
            traffic_tag=fake_port_ctx['vlan_tag'],
            address_bindings=pkt_classifiers,
            switch_profile_ids=switch_profile.build_switch_profile_ids(
                mock.Mock(), *profile_dicts),
            vif_type=fake_port_ctx['vif_type'], app_id=fake_port_ctx['app_id'],
            allocate_addresses=fake_port_ctx['allocate_addresses'])

        resp_body = {
            'logical_switch_id': fake_port['logical_switch_id'],
            'switching_profile_ids': fake_port['switching_profile_ids'],
            'attachment': {
                'attachment_type': 'VIF',
                'id': fake_port['attachment']['id'],
                'context': {
                    'resource_type': 'VifAttachmentContext',
                    'allocate_addresses': 'Both',
                    'parent_vif_id': fake_container_host_vif_id,
                    'traffic_tag': fake_port_ctx['vlan_tag'],
                    'app_id': fake_port_ctx['app_id'],
                    'vif_type': 'CHILD',
                }
            },
            'admin_state': 'UP',
            'address_bindings': fake_port['address_bindings']
        }

        test_client.assert_json_call(
            'post', mocked_resource,
            'https://1.2.3.4/api/v1/logical-ports',
            data=jsonutils.dumps(resp_body, sort_keys=True),
            headers=self.default_headers())

    def test_create_logical_port_admin_down(self):
        """Test creating port with admin_state down."""
        fake_port = test_constants.FAKE_PORT
        fake_port['admin_state'] = "DOWN"
        mocked_resource = self.get_mocked_resource(response=fake_port)

        result = mocked_resource.create(
            test_constants.FAKE_PORT['logical_switch_id'],
            test_constants.FAKE_PORT['attachment']['id'],
            tags={}, admin_state=False)

        self.assertEqual(fake_port, result)

    def test_delete_resource(self):
        """Test deleting port."""
        super(LogicalPortTestCase, self).test_delete_resource(
            extra_params='detach=true')

    def test_get_logical_port_by_attachment(self):
        """Test deleting port."""
        mocked_resource = self.get_mocked_resource()
        attachment_type = nsx_constants.ATTACHMENT_DHCP
        attachment_id = '1234'
        mocked_resource.get_by_attachment(attachment_type, attachment_id)
        test_client.assert_json_call(
            'get', mocked_resource,
            "https://1.2.3.4/api/v1/logical-ports/?attachment_type=%s"
            "&attachment_id=%s" % (attachment_type, attachment_id),
            headers=self.default_headers())

    def test_clear_port_bindings(self):
        fake_port = copy.copy(test_constants.FAKE_PORT)
        fake_port['address_bindings'] = ['a', 'b']
        mocked_resource = self.get_mocked_resource()

        def get_fake_port(*args, **kwargs):
            return copy.copy(fake_port)

        mocked_resource.client.get = get_fake_port
        mocked_resource.update(
            fake_port['id'], fake_port['attachment']['id'],
            address_bindings=[])

        fake_port['address_bindings'] = []
        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/logical-ports/%s' % fake_port['id'],
            data=jsonutils.dumps(fake_port, sort_keys=True),
            headers=self.default_headers())

    def test_create_logical_port_fail(self):
        """Test the failure of port creation."""
        fake_port = test_constants.FAKE_PORT.copy()
        profile_dicts = self._get_profile_dicts(fake_port)
        pkt_classifiers, binding_repr = self._get_pktcls_bindings()
        fake_port['address_bindings'] = binding_repr
        mocked_resource = self.get_mocked_resource(mock_validate=False)
        switch_profile = resources.SwitchingProfile
        try:
            mocked_resource.create(
                fake_port['logical_switch_id'],
                fake_port['attachment']['id'],
                address_bindings=pkt_classifiers,
                switch_profile_ids=switch_profile.build_switch_profile_ids(
                    mock.Mock(), *profile_dicts))
        except exceptions.ManagerError as e:
            self.assertIn(nsxlib_testcase.NSX_MANAGER, e.msg)

    def test_update_logical_port_no_addr_binding(self):
        fake_port = copy.deepcopy(test_constants.FAKE_CONTAINER_PORT)
        mocked_resource = self.get_mocked_resource()
        new_name = 'updated_port'
        new_desc = 'updated'
        fake_port_ctx = fake_port['attachment']['context']
        fake_container_host_vif_id = fake_port_ctx['container_host_vif_id']

        def get_fake_port(*args, **kwargs):
            return copy.copy(fake_port)

        mocked_resource.client.get = get_fake_port

        mocked_resource.update(
            fake_port['id'],
            fake_port['attachment']['id'],
            name=new_name,
            description=new_desc,
            parent_vif_id=fake_container_host_vif_id,
            traffic_tag=fake_port_ctx['vlan_tag'],
            vif_type=fake_port_ctx['vif_type'],
            app_id=fake_port_ctx['app_id'],
            allocate_addresses=fake_port_ctx['allocate_addresses'])

        fake_port['display_name'] = new_name
        fake_port['description'] = new_desc
        fake_port['attachment'] = {
            'attachment_type': 'VIF',
            'id': fake_port['attachment']['id'],
            'context': {
                'resource_type': 'VifAttachmentContext',
                'allocate_addresses': 'Both',
                'parent_vif_id': fake_container_host_vif_id,
                'traffic_tag': fake_port_ctx['vlan_tag'],
                'app_id': fake_port_ctx['app_id'],
                'vif_type': 'CHILD',
            }
        }

        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/logical-ports/%s' % fake_port['id'],
            data=jsonutils.dumps(fake_port, sort_keys=True),
            headers=self.default_headers())

    def test_update_logical_port_with_addr_binding(self):
        fake_port = copy.deepcopy(test_constants.FAKE_CONTAINER_PORT)
        mocked_resource = self.get_mocked_resource()
        new_name = 'updated_port'
        new_desc = 'updated'
        fake_port_ctx = fake_port['attachment']['context']
        fake_container_host_vif_id = fake_port_ctx['container_host_vif_id']
        pkt_classifiers, binding_repr = self._get_pktcls_bindings()

        def get_fake_port(*args, **kwargs):
            return copy.copy(fake_port)

        mocked_resource.client.get = get_fake_port

        mocked_resource.update(
            fake_port['id'],
            fake_port['attachment']['id'],
            name=new_name,
            description=new_desc,
            parent_vif_id=fake_container_host_vif_id,
            traffic_tag=fake_port_ctx['vlan_tag'],
            vif_type=fake_port_ctx['vif_type'],
            app_id=fake_port_ctx['app_id'],
            allocate_addresses=fake_port_ctx['allocate_addresses'],
            address_bindings=pkt_classifiers)

        fake_port['display_name'] = new_name
        fake_port['description'] = new_desc
        fake_port['attachment'] = {
            'attachment_type': 'VIF',
            'id': fake_port['attachment']['id'],
            'context': {
                'resource_type': 'VifAttachmentContext',
                'allocate_addresses': 'Both',
                'parent_vif_id': fake_container_host_vif_id,
                'traffic_tag': fake_port_ctx['vlan_tag'],
                'app_id': fake_port_ctx['app_id'],
                'vif_type': 'CHILD',
            }
        }
        fake_port['address_bindings'] = binding_repr
        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/logical-ports/%s' % fake_port['id'],
            data=jsonutils.dumps(fake_port, sort_keys=True),
            headers=self.default_headers())


class LogicalRouterTestCase(BaseTestResource):

    def setUp(self):
        super(LogicalRouterTestCase, self).setUp(
            core_resources.NsxLibLogicalRouter)

    def test_create_logical_router(self):
        """Test creating a router returns the correct response and 201 status.

        """
        fake_router = test_constants.FAKE_ROUTER.copy()
        router = self.get_mocked_resource()
        tier0_router = True
        description = 'dummy'
        router.create(fake_router['display_name'], None, None, tier0_router,
                      description=description)

        data = {
            'display_name': fake_router['display_name'],
            'router_type': 'TIER0' if tier0_router else 'TIER1',
            'tags': None,
            'description': description
        }

        test_client.assert_json_call(
            'post', router,
            'https://1.2.3.4/api/v1/logical-routers',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_force_delete_logical_router(self):
        """Test force deleting router"""
        router = self.get_mocked_resource()
        uuid = test_constants.FAKE_ROUTER['id']
        router.delete(uuid, True)
        test_client.assert_json_call(
            'delete', router,
            'https://1.2.3.4/api/v1/logical-routers/%s?force=True' % uuid,
            headers=self.default_headers())

    def test_list_logical_router_by_type(self):
        router = self.get_mocked_resource()
        router_type = 'TIER0'
        router.list(router_type=router_type)
        test_client.assert_json_call(
            'get', router,
            'https://1.2.3.4/api/v1/logical-routers?router_type=%s' %
            router_type)

    def test_get_logical_router_fw_section(self):
        fake_router = test_constants.FAKE_ROUTER.copy()
        router = self.get_mocked_resource()
        section_id = router.get_firewall_section_id(
            test_constants.FAKE_ROUTER_UUID, router_body=fake_router)
        self.assertEqual(test_constants.FAKE_ROUTER_FW_SEC_UUID, section_id)

    def _test_nat_rule_create(self, nsx_version, add_bypas_arg):
        router = self.get_mocked_resource()
        action = 'SNAT'
        translated_net = '1.1.1.1'
        priority = 10

        data = {
            'action': action,
            'enabled': True,
            'translated_network': translated_net,
            'rule_priority': priority
        }
        if add_bypas_arg:
            # Expect nat_pass to be sent to the backend
            data['nat_pass'] = False

        # Ignoring 'bypass_firewall' with version 1.1
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value=nsx_version):
            router.add_nat_rule(test_constants.FAKE_ROUTER_UUID,
                                action=action,
                                translated_network=translated_net,
                                rule_priority=priority,
                                bypass_firewall=False)
            test_client.assert_json_call(
                'post', router,
                ('https://1.2.3.4/api/v1/logical-routers/%s/nat/rules' %
                    test_constants.FAKE_ROUTER_UUID),
                data=jsonutils.dumps(data, sort_keys=True),
                headers=self.default_headers())

    def test_nat_rule_create_v1(self):
        # Ignoring 'bypass_firewall' with version 1.1
        self._test_nat_rule_create('1.1.0', False)

    def test_nat_rule_create_v2(self):
        # Sending 'bypass_firewall' with version 1.1
        self._test_nat_rule_create('2.0.0', True)

    def test_nat_rule_list(self):
        router = self.get_mocked_resource()
        router.list_nat_rules(test_constants.FAKE_ROUTER_UUID)
        test_client.assert_json_call(
            'get', router,
            ('https://1.2.3.4/api/v1/logical-routers/%s/nat/rules' %
                test_constants.FAKE_ROUTER_UUID),
            headers=self.default_headers())

    def test_nat_rule_update(self):
        router = self.get_mocked_resource()
        rule_id = '123'
        with mock.patch.object(router.client, 'get',
                               return_value={'id': rule_id}):
            router.update_nat_rule(test_constants.FAKE_ROUTER_UUID,
                                   rule_id, nat_pass=False)
            data = {'id': rule_id, 'nat_pass': False}
            test_client.assert_json_call(
                'put', router,
                ('https://1.2.3.4/api/v1/logical-routers/%s/nat/rules/%s' %
                    (test_constants.FAKE_ROUTER_UUID, rule_id)),
                data=jsonutils.dumps(data, sort_keys=True),
                headers=self.default_headers())

    def test_delete_nat_rule_by_gw(self):
        router = self.get_mocked_resource()
        rule_id = '123'
        router_id = test_constants.FAKE_ROUTER_UUID
        gw_ip = '3.3.3.3'
        existing_rules = [{
            'translated_network': gw_ip,
            'logical_router_id': router_id,
            'id': rule_id,
            'action': 'SNAT',
            'resource_type': 'NatRule'}]
        with mock.patch.object(router.client, 'list',
                               return_value={'results': existing_rules}):
            router.delete_nat_rule_by_values(router_id,
                                             translated_network=gw_ip)
            test_client.assert_json_call(
                'delete', router,
                ('https://1.2.3.4/api/v1/logical-routers/%s/nat/rules/%s' %
                    (router_id, rule_id)),
                headers=self.default_headers())

    def test_delete_nat_rule_by_gw_and_source(self):
        router = self.get_mocked_resource()
        rule_id = '123'
        router_id = test_constants.FAKE_ROUTER_UUID
        gw_ip = '3.3.3.3'
        source_net = '4.4.4.4'
        existing_rules = [{
            'translated_network': gw_ip,
            'logical_router_id': router_id,
            'id': rule_id,
            'match_source_network': source_net,
            'action': 'SNAT',
            'resource_type': 'NatRule'}]
        with mock.patch.object(router.client, 'list',
                               return_value={'results': existing_rules}):
            router.delete_nat_rule_by_values(router_id,
                                             translated_network=gw_ip,
                                             match_source_network=source_net)
            test_client.assert_json_call(
                'delete', router,
                ('https://1.2.3.4/api/v1/logical-routers/%s/nat/rules/%s' %
                    (router_id, rule_id)),
                headers=self.default_headers())

    def test_update_advertisement(self):
        router = self.get_mocked_resource()
        router_id = test_constants.FAKE_ROUTER_UUID
        data = {'advertise_nat_routes': 'a',
                'advertise_nsx_connected_routes': 'b',
                'advertise_static_routes': False,
                'enabled': True,
                'advertise_lb_vip': False,
                'advertise_lb_snat_ip': False}
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.1.0'), \
            mock.patch.object(router.client, 'get',
                              return_value={}):
            router.update_advertisement(
                router_id, **data)
            test_client.assert_json_call(
                'put', router,
                ('https://1.2.3.4/api/v1/logical-routers/%s/routing/'
                 'advertisement' % router_id),
                data=jsonutils.dumps(data, sort_keys=True),
                headers=self.default_headers())

    def test_update_advertisement_no_lb(self):
        router = self.get_mocked_resource()
        router_id = test_constants.FAKE_ROUTER_UUID
        data = {'advertise_nat_routes': 'a',
                'advertise_nsx_connected_routes': 'b',
                'advertise_static_routes': False,
                'enabled': True}
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='1.1.0'), \
            mock.patch.object(router.client, 'get',
                              return_value={}):
            # lb args will be ignored on this nsx version
            router.update_advertisement(
                router_id,
                advertise_lb_vip=False,
                advertise_lb_snat_ip=False,
                **data)
            test_client.assert_json_call(
                'put', router,
                ('https://1.2.3.4/api/v1/logical-routers/%s/routing/'
                 'advertisement' % router_id),
                data=jsonutils.dumps(data, sort_keys=True),
                headers=self.default_headers())

    def test_update_advertisement_rules(self):
        router = self.get_mocked_resource()
        router_id = test_constants.FAKE_ROUTER_UUID
        rules = [{"action": "ALLOW",
                  "networks": ["44.0.0.0/20"],
                  "display_name": "rule1"},
                 {"action": "ALLOW",
                  "networks": ["6.60.0.0/20"],
                  "display_name": "rule2"}]
        with mock.patch.object(router.client, 'get',
                               return_value={}):
            router.update_advertisement_rules(router_id, rules)
            test_client.assert_json_call(
                'put', router,
                ('https://1.2.3.4/api/v1/logical-routers/%s/routing/'
                 'advertisement/rules' % router_id),
                data=jsonutils.dumps({'rules': rules}, sort_keys=True),
                headers=self.default_headers())

    def test_get_advertisement_rules(self):
        router = self.get_mocked_resource()
        router_id = test_constants.FAKE_ROUTER_UUID
        router.get_advertisement_rules(router_id)
        test_client.assert_json_call(
            'get', router,
            ('https://1.2.3.4/api/v1/logical-routers/%s/routing/'
             'advertisement/rules' % router_id),
            headers=self.default_headers())


class LogicalRouterPortTestCase(BaseTestResource):

    def setUp(self):
        super(LogicalRouterPortTestCase, self).setUp(
            resources.LogicalRouterPort)

    def test_create_logical_router_port(self):
        """Test creating a router port.

        returns the correct response and 201 status
        """
        fake_router_port = test_constants.FAKE_ROUTER_PORT.copy()
        fake_relay_uuid = uuidutils.generate_uuid()
        lrport = self.get_mocked_resource()

        data = {
            'display_name': fake_router_port['display_name'],
            'logical_router_id': fake_router_port['logical_router_id'],
            'resource_type': fake_router_port['resource_type'],
            'tags': [],
            'service_bindings': [{'service_id': {
                'target_type': 'LogicalService',
                'target_id': fake_relay_uuid}}]
        }

        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.0.0'):
            lrport.create(fake_router_port['logical_router_id'],
                          fake_router_port['display_name'],
                          None,
                          fake_router_port['resource_type'],
                          None, None, None,
                          relay_service_uuid=fake_relay_uuid)

            test_client.assert_json_call(
                'post', lrport,
                'https://1.2.3.4/api/v1/logical-router-ports',
                data=jsonutils.dumps(data, sort_keys=True),
                headers=self.default_headers())

    def test_logical_router_port_max_attempts(self):
        """Test a router port api has the configured retries."""
        lrport = self.get_mocked_resource()

        self.assertEqual(nsxlib_testcase.NSX_MAX_ATTEMPTS,
                         lrport.client.max_attempts)

    def test_update_logical_router_port(self):
        fake_router_port = test_constants.FAKE_ROUTER_PORT.copy()
        uuid = fake_router_port['id']
        fake_relay_uuid = uuidutils.generate_uuid()
        lrport = self.get_mocked_resource()
        with mock.patch.object(lrport.client, 'get',
                               return_value=fake_router_port),\
            mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                       return_value='2.0.0'):
            lrport.update(uuid, relay_service_uuid=fake_relay_uuid)
            data = {
                'id': uuid,
                'display_name': fake_router_port['display_name'],
                'logical_router_id': fake_router_port['logical_router_id'],
                'resource_type': fake_router_port['resource_type'],
                "revision": 0,
                'service_bindings': [{'service_id': {
                    'target_type': 'LogicalService',
                    'target_id': fake_relay_uuid}}]
            }

            test_client.assert_json_call(
                'put', lrport,
                'https://1.2.3.4/api/v1/logical-router-ports/%s' % uuid,
                data=jsonutils.dumps(data, sort_keys=True),
                headers=self.default_headers())

    def test_get_logical_router_port_by_router_id(self):
        """Test getting a router port by router id."""
        fake_router_port = test_constants.FAKE_ROUTER_PORT.copy()
        resp_resources = {'results': [fake_router_port]}
        lrport = self.get_mocked_resource(response=resp_resources)

        router_id = fake_router_port['logical_router_id']
        result = lrport.get_by_router_id(router_id)
        self.assertEqual(fake_router_port, result[0])
        test_client.assert_json_call(
            'get', lrport,
            'https://1.2.3.4/api/v1/logical-router-ports/?'
            'logical_router_id=%s' % router_id,
            headers=self.default_headers())

    def test_get_logical_router_port_by_switch_id(self):
        """Test getting a router port by switch id."""
        fake_router_port = test_constants.FAKE_ROUTER_PORT.copy()
        resp_resources = {
            'result_count': 1,
            'results': [fake_router_port]
        }
        lrport = self.get_mocked_resource(response=resp_resources)

        switch_id = test_constants.FAKE_SWITCH_UUID
        lrport.get_by_lswitch_id(switch_id)
        test_client.assert_json_call(
            'get', lrport,
            'https://1.2.3.4/api/v1/logical-router-ports/?'
            'logical_switch_id=%s' % switch_id,
            headers=self.default_headers())


class IpPoolTestCase(BaseTestResource):

    def setUp(self):
        super(IpPoolTestCase, self).setUp(resources.IpPool)

    def test_create_ip_pool_all_args(self):
        """Test creating an IP pool

        returns the correct response and 201 status
        """
        pool = self.get_mocked_resource()

        display_name = 'dummy'
        gateway_ip = '1.1.1.1'
        ranges = [{'start': '2.2.2.0', 'end': '2.2.2.255'},
                  {'start': '3.2.2.0', 'end': '3.2.2.255'}]
        cidr = '2.2.2.0/24'
        description = 'desc'
        dns_nameserver = '7.7.7.7'
        pool.create(cidr, allocation_ranges=ranges,
                    display_name=display_name,
                    gateway_ip=gateway_ip,
                    description=description,
                    dns_nameservers=[dns_nameserver])

        data = {
            'display_name': display_name,
            'description': description,
            'subnets': [{
                'gateway_ip': gateway_ip,
                'allocation_ranges': ranges,
                'cidr': cidr,
                'dns_nameservers': [dns_nameserver]
            }]
        }

        test_client.assert_json_call(
            'post', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_create_ip_pool_minimal_args(self):
        pool = self.get_mocked_resource()

        ranges = [{'start': '2.2.2.0', 'end': '2.2.2.255'},
                  {'start': '3.2.2.0', 'end': '3.2.2.255'}]
        cidr = '2.2.2.0/24'
        pool.create(cidr, allocation_ranges=ranges)

        data = {
            'subnets': [{
                'allocation_ranges': ranges,
                'cidr': cidr,
            }]
        }

        test_client.assert_json_call(
            'post', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_create_ip_pool_no_ranges_with_gateway(self):
        pool = self.get_mocked_resource()
        cidr = '2.2.2.0/30'
        gateway_ip = '2.2.2.1'
        pool.create(cidr, allocation_ranges=None, gateway_ip=gateway_ip)
        exp_ranges = [{'start': '2.2.2.0', 'end': '2.2.2.0'},
                      {'start': '2.2.2.2', 'end': '2.2.2.3'}]

        data = {
            'subnets': [{
                'gateway_ip': gateway_ip,
                'allocation_ranges': exp_ranges,
                'cidr': cidr,
            }]
        }

        test_client.assert_json_call(
            'post', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_create_ip_pool_no_ranges_no_gateway(self):
        pool = self.get_mocked_resource()
        cidr = '2.2.2.0/30'
        pool.create(cidr, allocation_ranges=None)
        exp_ranges = [{'start': '2.2.2.0', 'end': '2.2.2.3'}]

        data = {
            'subnets': [{
                'allocation_ranges': exp_ranges,
                'cidr': cidr,
            }]
        }

        test_client.assert_json_call(
            'post', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_create_ip_pool_no_cidr(self):
        pool = self.get_mocked_resource()
        gateway_ip = '1.1.1.1'
        ranges = [{'start': '2.2.2.0', 'end': '2.2.2.255'},
                  {'start': '3.2.2.0', 'end': '3.2.2.255'}]
        cidr = None

        try:
            pool.create(cidr, allocation_ranges=ranges,
                        gateway_ip=gateway_ip)
        except exceptions.InvalidInput:
            # This call should fail
            pass
        else:
            self.fail("shouldn't happen")

    def test_update_ip_pool_name(self):
        fake_ip_pool = test_constants.FAKE_IP_POOL.copy()
        pool = self.get_mocked_resource(response=fake_ip_pool)

        uuid = fake_ip_pool['id']
        new_name = 'new_name'
        pool.update(uuid, display_name=new_name)
        fake_ip_pool['display_name'] = new_name
        test_client.assert_json_call(
            'put', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools/%s' % uuid,
            data=jsonutils.dumps(fake_ip_pool, sort_keys=True),
            headers=self.default_headers())

    def test_update_ip_pool_gateway(self):
        fake_ip_pool = test_constants.FAKE_IP_POOL.copy()
        pool = self.get_mocked_resource(response=fake_ip_pool)

        uuid = fake_ip_pool['id']
        new_gateway = '1.0.0.1'
        pool.update(uuid, gateway_ip=new_gateway)
        fake_ip_pool["subnets"][0]['gateway_ip'] = new_gateway
        test_client.assert_json_call(
            'put', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools/%s' % uuid,
            data=jsonutils.dumps(fake_ip_pool, sort_keys=True),
            headers=self.default_headers())

    def test_update_ip_pool_delete_gateway(self):
        fake_ip_pool = test_constants.FAKE_IP_POOL.copy()
        pool = self.get_mocked_resource(response=fake_ip_pool)

        uuid = fake_ip_pool['id']
        pool.update(uuid, gateway_ip=None)
        del fake_ip_pool["subnets"][0]['gateway_ip']
        test_client.assert_json_call(
            'put', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools/%s' % uuid,
            data=jsonutils.dumps(fake_ip_pool, sort_keys=True),
            headers=self.default_headers())

    def test_allocate_ip_from_pool(self):
        pool = self.get_mocked_resource()

        uuid = test_constants.FAKE_IP_POOL['id']
        addr = '1.1.1.1'
        pool.allocate(uuid, ip_addr=addr)

        data = {'allocation_id': addr}
        test_client.assert_json_call(
            'post', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools/%s?action=ALLOCATE' % uuid,
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_release_ip_to_pool(self):
        pool = self.get_mocked_resource()

        uuid = test_constants.FAKE_IP_POOL['id']
        addr = '1.1.1.1'
        pool.release(uuid, addr)

        data = {'allocation_id': addr}
        test_client.assert_json_call(
            'post', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools/%s?action=RELEASE' % uuid,
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_get_ip_pool_allocations(self):
        """Test getting a router port by router id"""
        fake_ip_pool = test_constants.FAKE_IP_POOL.copy()
        pool = self.get_mocked_resource(response=fake_ip_pool)

        uuid = fake_ip_pool['id']
        result = pool.get_allocations(uuid)
        self.assertEqual(fake_ip_pool, result)
        test_client.assert_json_call(
            'get', pool,
            'https://1.2.3.4/api/v1/pools/ip-pools/%s/allocations' % uuid,
            headers=self.default_headers())


class TestNsxSearch(nsxlib_testcase.NsxClientTestCase):

    def test_nsx_search_tags(self):
        """Test search of resources with the specified tag."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            user_tags = [{'scope': 'user', 'tag': 'k8s'}]
            query = self.nsxlib._build_query(tags=user_tags)
            self.nsxlib.search_by_tags(tags=user_tags)
            search.assert_called_with('search?query=%s' % query)

    def test_nsx_search_tags_scope_only(self):
        """Test search of resources with the specified tag."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            user_tags = [{'scope': 'user'}]
            query = self.nsxlib._build_query(tags=user_tags)
            self.nsxlib.search_by_tags(tags=user_tags)
            search.assert_called_with('search?query=%s' % query)

    def test_nsx_search_tags_tag_only(self):
        """Test search of resources with the specified tag."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            user_tags = [{'tag': 'k8s'}]
            query = self.nsxlib._build_query(tags=user_tags)
            self.nsxlib.search_by_tags(tags=user_tags)
            search.assert_called_with('search?query=%s' % query)

    def test_nsx_search_tags_tag_and_scope(self):
        """Test search of resources with the specified tag."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            user_tags = [{'tag': 'k8s'}, {'scope': 'user'}]
            query = self.nsxlib._build_query(tags=user_tags)
            self.nsxlib.search_by_tags(tags=user_tags)
            search.assert_called_with('search?query=%s' % query)

    def test_nsx_search_tags_and_resource_type(self):
        """Test search of specified resource with the specified tag."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            user_tags = [{'scope': 'user', 'tag': 'k8s'}]
            res_type = 'LogicalPort'
            query = self.nsxlib._build_query(tags=user_tags)
            # Add resource_type to the query
            query = "resource_type:%s AND %s" % (res_type, query)
            self.nsxlib.search_by_tags(tags=user_tags, resource_type=res_type)
            search.assert_called_with('search?query=%s' % query)

    def test_nsx_search_tags_and_cursor(self):
        """Test search of resources with the specified tag and cursor."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            user_tags = [{'scope': 'user', 'tag': 'k8s'}]
            query = self.nsxlib._build_query(tags=user_tags)
            self.nsxlib.search_by_tags(tags=user_tags, cursor=50)
            search.assert_called_with('search?query=%s&cursor=50' % query)

    def test_nsx_search_tags_and_page_size(self):
        """Test search of resources with the specified tag and page size."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            user_tags = [{'scope': 'user', 'tag': 'k8s'}]
            query = self.nsxlib._build_query(tags=user_tags)
            self.nsxlib.search_by_tags(tags=user_tags, page_size=100)
            search.assert_called_with('search?query=%s&page_size=100' % query)

    def test_nsx_search_invalid_query_fail(self):
        """Test search query failure for missing tag argument."""
        self.assertRaises(exceptions.NsxSearchInvalidQuery,
                          self.nsxlib.search_by_tags,
                          tags=None, resource_type=None)

    def test_nsx_search_invalid_tags_fail(self):
        """Test search of resources with the invalid tag."""
        user_tags = [{'scope': 'user', 'invalid_tag_key': 'k8s'}]
        self.assertRaises(exceptions.NsxSearchInvalidQuery,
                          self.nsxlib._build_query,
                          tags=user_tags)

    def test_nsx_search_all_by_tags(self):
        """Test search all of resources with the specified tag."""
        with mock.patch.object(self.nsxlib.client, 'url_get') as search:
            search.side_effect = [
                {"cursor": "2",
                 "result_count": 3,
                 "results": [{"id": "s1"},
                             {"id": "s2"}]},
                {"cursor": "3",
                 "result_count": 3,
                 "results": [{"id": "s3"}]}]
            user_tags = [{'scope': 'user', 'tag': 'k8s'}]
            query = self.nsxlib._build_query(tags=user_tags)
            results = self.nsxlib.search_all_by_tags(tags=user_tags)
            search.assert_has_calls([
                mock.call('search?query=%s' % query),
                mock.call('search?query=%s&cursor=2' % query)])
            self.assertEqual(3, len(results))

    def test_get_id_by_resource_and_tag(self):
        id = 'test'
        scope = 'user'
        tag = 'k8s'
        res_type = 'LogicalPort'
        results = {'result_count': 1, 'results': [{'id': id}]}
        with mock.patch.object(self.nsxlib.client, 'url_get',
                               return_value=results):
            actual_id = self.nsxlib.get_id_by_resource_and_tag(
                res_type, scope, tag)
            self.assertEqual(id, actual_id)

    def test_get_id_by_resource_and_tag_not_found(self):
        scope = 'user'
        tag = 'k8s'
        res_type = 'LogicalPort'
        results = {'result_count': 0, 'results': []}
        with mock.patch.object(self.nsxlib.client, 'url_get',
                               return_value=results):
            self.assertRaises(exceptions.ResourceNotFound,
                              self.nsxlib.get_id_by_resource_and_tag,
                              res_type, scope, tag, alert_not_found=True)

    def test_get_id_by_resource_and_tag_multiple(self):
        scope = 'user'
        tag = 'k8s'
        res_type = 'LogicalPort'
        results = {'result_count': 2, 'results': [{'id': '1'}, {'id': '2'}]}
        with mock.patch.object(self.nsxlib.client, 'url_get',
                               return_value=results):
            self.assertRaises(exceptions.ManagerError,
                              self.nsxlib.get_id_by_resource_and_tag,
                              res_type, scope, tag, alert_multiple=True)


class TransportZone(BaseTestResource):

    def setUp(self):
        super(TransportZone, self).setUp(core_resources.NsxLibTransportZone)

    def test_get_transport_zone_type(self):
        fake_tz = test_constants.FAKE_TZ.copy()
        tz = self.get_mocked_resource()
        with mock.patch.object(tz.client, 'url_get', return_value=fake_tz):
            tz_type = tz.get_transport_type(fake_tz['id'])
            self.assertEqual(tz.TRANSPORT_TYPE_OVERLAY, tz_type)

            # call it again to test it when cached
            tz_type = tz.get_transport_type(fake_tz['id'])
            self.assertEqual(tz.TRANSPORT_TYPE_OVERLAY, tz_type)

    def test_get_host_switch_mode(self):
        fake_tz = test_constants.FAKE_TZ.copy()
        tz = self.get_mocked_resource()
        with mock.patch.object(tz.client, 'url_get', return_value=fake_tz):
            tz_mode = tz.get_host_switch_mode(fake_tz['id'])
            self.assertEqual(tz.HOST_SWITCH_MODE_STANDARD, tz_mode)


class MetadataProxy(BaseTestResource):

    def setUp(self):
        super(MetadataProxy, self).setUp(core_resources.NsxLibMetadataProxy)

    def test_update_metadata_proxy(self):
        fake_md = test_constants.FAKE_MD.copy()
        md = self.get_mocked_resource()
        new_url = "http://2.2.2.20:3500/xyz"
        new_secret = 'abc'
        new_edge = uuidutils.generate_uuid()
        with mock.patch.object(md.client, 'url_get', return_value=fake_md):
            md.update(fake_md['id'], server_url=new_url, secret=new_secret,
                      edge_cluster_id=new_edge)
            fake_md.update({'metadata_server_url': new_url,
                            'secret': new_secret,
                            'edge_cluster_id': new_edge})
            test_client.assert_json_call(
                'put', md,
                'https://1.2.3.4/api/v1/md-proxies/%s' % fake_md['id'],
                data=jsonutils.dumps(fake_md, sort_keys=True),
                headers=self.default_headers())


class NsxLibSwitchTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibSwitchTestCase, self).setUp(
            core_resources.NsxLibLogicalSwitch)
        self._tz_id = uuidutils.generate_uuid()

    def _create_body(self, admin_state=nsx_constants.ADMIN_STATE_UP,
                     vlan_id=None, description=None, trunk_vlan=None):
        body = {
            "transport_zone_id": self._tz_id,
            "replication_mode": "MTEP",
            "display_name": "fake_name",
            "tags": [],
            "admin_state": admin_state
        }
        if vlan_id:
            body['vlan'] = vlan_id
        if description is not None:
            body['description'] = description
        if trunk_vlan:
            body['vlan_trunk_spec'] = {
                'vlan_ranges': [{'start': trunk_vlan[0],
                                 'end': trunk_vlan[1]}]}
        return body

    def test_create_logical_switch(self):
        """Test creating a switch returns the correct response and 200 status

        """
        desc = 'dummy'
        ls = self.get_mocked_resource()
        ls.create(mocks.FAKE_NAME, self._tz_id, [],
                  description=desc)
        data = self._create_body(description=desc)
        test_client.assert_json_call(
            'post', ls,
            'https://1.2.3.4/api/v1/logical-switches',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_create_logical_switch_admin_down(self):
        """Test creating switch with admin_state down"""
        ls = self.get_mocked_resource()
        ls.create(mocks.FAKE_NAME, self._tz_id, [],
                  admin_state=False)
        data = self._create_body(admin_state=nsx_constants.ADMIN_STATE_DOWN)
        test_client.assert_json_call(
            'post', ls,
            'https://1.2.3.4/api/v1/logical-switches',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_create_logical_switch_vlan(self):
        """Test creating switch with provider:network_type VLAN"""
        ls = self.get_mocked_resource()
        vlan_id = '123'
        ls.create(mocks.FAKE_NAME, self._tz_id, [],
                  vlan_id=vlan_id)
        data = self._create_body(vlan_id=vlan_id)
        test_client.assert_json_call(
            'post', ls,
            'https://1.2.3.4/api/v1/logical-switches',
            data=jsonutils.dumps(data, sort_keys=True),
            headers=self.default_headers())

    def test_create_logical_switch_trunk(self):
        """Test creating switch with trunk vlan"""
        ls = self.get_mocked_resource()
        trunk_vlan = [10, 20]
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.2.0'):
            ls.create(mocks.FAKE_NAME, self._tz_id, [],
                      trunk_vlan_range=trunk_vlan)
            data = self._create_body(trunk_vlan=trunk_vlan)
            test_client.assert_json_call(
                'post', ls,
                'https://1.2.3.4/api/v1/logical-switches',
                data=jsonutils.dumps(data, sort_keys=True),
                headers=self.default_headers())

    def test_create_logical_switch_trunk_not_supported(self):
        """Test creating switch with trunk vlan without the support"""
        ls = self.get_mocked_resource()
        trunk_vlan = [10, 20]
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.0.0'):
            self.assertRaises(exceptions.InvalidInput,
                              ls.create,
                              mocks.FAKE_NAME, self._tz_id, [],
                              trunk_vlan_range=trunk_vlan)

    def test_create_logical_switch_trunk_with_vlan(self):
        """Test creating switch with trunk vlan and vlan tag"""
        ls = self.get_mocked_resource()
        trunk_vlan = [10, 20]
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.2.0'):
            self.assertRaises(exceptions.InvalidInput,
                              ls.create,
                              mocks.FAKE_NAME, self._tz_id, [],
                              trunk_vlan_range=trunk_vlan,
                              vlan_id='111')

    def test_create_logical_switch_illegal_trunk(self):
        """Test creating switch with illegal trunk vlan"""
        ls = self.get_mocked_resource()
        trunk_vlan = [10]
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.2.0'):
            self.assertRaises(exceptions.InvalidInput,
                              ls.create,
                              mocks.FAKE_NAME, self._tz_id, [],
                              trunk_vlan_range=trunk_vlan)

    def test_delete_resource(self):
        """Test deleting switch"""
        super(NsxLibSwitchTestCase, self).test_delete_resource(
            extra_params='detach=true&cascade=true')


class NsxLibPortMirrorTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibPortMirrorTestCase, self).setUp(
            core_resources.NsxLibPortMirror)


class NsxLibBridgeEndpointTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibBridgeEndpointTestCase, self).setUp(
            core_resources.NsxLibBridgeEndpoint)


class NsxLibEdgeClusterTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibEdgeClusterTestCase, self).setUp(
            core_resources.NsxLibEdgeCluster)


class NsxLibDhcpProfileTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibDhcpProfileTestCase, self).setUp(
            core_resources.NsxLibDhcpProfile)


class NsxLibDhcpRelayServiceTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibDhcpRelayServiceTestCase, self).setUp(
            core_resources.NsxLibDhcpRelayService)

    def test_server_ips(self):
        fake_srv = test_constants.FAKE_RELAY_SERVICE.copy()
        relay_service = self.get_mocked_resource()
        with mock.patch.object(relay_service.client, 'url_get',
                               return_value=fake_srv), \
            mock.patch.object(self.nsxlib.client, 'url_get',
                              return_value=test_constants.FAKE_RELAY_PROFILE):
            server_ips = relay_service.get_server_ips(fake_srv['id'])
            self.assertEqual(1, len(server_ips))
            self.assertEqual(test_constants.FAKE_RELAY_SERVER,
                             server_ips[0])


class NsxLibDhcpRelayProfileTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibDhcpRelayProfileTestCase, self).setUp(
            core_resources.NsxLibDhcpRelayProfile)

    def test_server_ips(self):
        fake_prf = test_constants.FAKE_RELAY_PROFILE.copy()
        relay_profile = self.get_mocked_resource()
        with mock.patch.object(relay_profile.client, 'url_get',
                               return_value=fake_prf):
            server_ips = relay_profile.get_server_ips(fake_prf['id'])
            self.assertEqual(1, len(server_ips))
            self.assertEqual(test_constants.FAKE_RELAY_SERVER,
                             server_ips[0])


class NsxLibBridgeClusterTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibBridgeClusterTestCase, self).setUp(
            core_resources.NsxLibBridgeCluster)


class NsxLibIpBlockSubnetTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibIpBlockSubnetTestCase, self).setUp(
            core_resources.NsxLibIpBlockSubnet)

    def test_list_all(self):
        if not self.resource:
            return
        mocked_resource = self.get_mocked_resource()
        block_id = '7'
        mocked_resource.list(block_id)
        test_client.assert_json_call(
            'get', mocked_resource,
            'https://1.2.3.4/api/v1/%s?block_id=%s' %
            (mocked_resource.uri_segment, block_id),
            headers=self.default_headers())


class NsxLibIpBlockTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibIpBlockTestCase, self).setUp(
            core_resources.NsxLibIpBlock)


class NsxLibFabricVirtualInterfaceTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibFabricVirtualInterfaceTestCase, self).setUp(
            core_resources.NsxLibFabricVirtualInterface)

    def test_get_by_owner_vm_id(self):
        mocked_resource = self.get_mocked_resource()
        vm_id = uuidutils.generate_uuid()
        mocked_resource.get_by_owner_vm_id(vm_id)
        test_client.assert_json_call(
            'get', mocked_resource,
            'https://1.2.3.4/api/v1/%s?owner_vm_id=%s' %
            (mocked_resource.uri_segment, vm_id),
            headers=self.default_headers())


class NsxLibFabricVirtualMachineTestCase(BaseTestResource):

    def setUp(self):
        super(NsxLibFabricVirtualMachineTestCase, self).setUp(
            core_resources.NsxLibFabricVirtualMachine)

    def test_get_by_display_name(self):
        mocked_resource = self.get_mocked_resource()
        display_name = 'some-vm-name'
        mocked_resource.get_by_display_name(display_name)
        test_client.assert_json_call(
            'get', mocked_resource,
            'https://1.2.3.4/api/v1/%s?display_name=%s' %
            (mocked_resource.uri_segment, display_name),
            headers=self.default_headers())


class LogicalDhcpServerTestCase(BaseTestResource):

    def setUp(self):
        super(LogicalDhcpServerTestCase, self).setUp(
            resources.LogicalDhcpServer)

    def test_update_empty_dhcp_server(self):
        mocked_resource = self.get_mocked_resource()
        server_uuid = 'server-uuid'
        ip = '1.1.1.1'

        with mock.patch.object(mocked_resource.client, "get", return_value={}):
            mocked_resource.update(server_uuid, server_ip=ip)
        body = {'ipv4_dhcp_server': {'dhcp_server_ip': ip}}

        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/%s/%s' %
            (mocked_resource.uri_segment, server_uuid),
            data=jsonutils.dumps(body, sort_keys=True),
            headers=self.default_headers())

    def test_update_dhcp_server_new_val(self):
        mocked_resource = self.get_mocked_resource()
        server_uuid = 'server-uuid'
        ip = '1.1.1.1'
        domain_name = 'dummy'
        existing_server = {'ipv4_dhcp_server': {'domain_name': domain_name}}

        # add the server ip
        with mock.patch.object(mocked_resource.client, "get",
                               return_value=existing_server):
            mocked_resource.update(server_uuid, server_ip=ip)

        existing_server['ipv4_dhcp_server']['dhcp_server_ip'] = ip
        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/%s/%s' %
            (mocked_resource.uri_segment, server_uuid),
            data=jsonutils.dumps(existing_server, sort_keys=True),
            headers=self.default_headers())

    def test_update_dhcp_server_replace_val(self):
        mocked_resource = self.get_mocked_resource()
        server_uuid = 'server-uuid'
        ip = '1.1.1.1'
        domain_name = 'dummy'
        existing_server = {'ipv4_dhcp_server': {'domain_name': domain_name,
                                                'dhcp_server_ip': ip}}

        # replace the server ip
        new_ip = '2.2.2.2'
        with mock.patch.object(mocked_resource.client, "get",
                               return_value=existing_server):
            mocked_resource.update(server_uuid, server_ip=new_ip)

        existing_server['ipv4_dhcp_server']['dhcp_server_ip'] = new_ip
        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/%s/%s' %
            (mocked_resource.uri_segment, server_uuid),
            data=jsonutils.dumps(existing_server, sort_keys=True),
            headers=self.default_headers())

    def test_create_binding(self):
        mocked_resource = self.get_mocked_resource()
        server_uuid = 'server-uuid'
        mac = 'aa:bb:cc:dd:ee:ff'
        ip = '1.1.1.1'
        host = 'host'
        mocked_resource.create_binding(server_uuid, mac, ip, hostname=host)
        body = {
            'mac_address': mac,
            'ip_address': ip,
            'host_name': host,
        }
        test_client.assert_json_call(
            'post', mocked_resource,
            'https://1.2.3.4/api/v1/%s/%s/static-bindings' %
            (mocked_resource.uri_segment, server_uuid),
            data=jsonutils.dumps(body, sort_keys=True),
            headers=self.default_headers())

    def test_get_binding(self):
        mocked_resource = self.get_mocked_resource()
        server_uuid = 'server-uuid'
        binding_uuid = 'binding-uuid'
        mocked_resource.get_binding(server_uuid, binding_uuid)
        test_client.assert_json_call(
            'get', mocked_resource,
            'https://1.2.3.4/api/v1/%s/%s/static-bindings/%s' %
            (mocked_resource.uri_segment, server_uuid, binding_uuid),
            headers=self.default_headers())

    def test_update_binding(self):
        mocked_resource = self.get_mocked_resource()
        server_uuid = 'server-uuid'
        binding_uuid = 'binding-uuid'
        mac = 'aa:bb:cc:dd:ee:ff'
        new_mac = 'dd:bb:cc:dd:ee:ff'
        ip = '1.1.1.1'
        host = 'host'
        body = {
            'mac_address': mac,
            'ip_address': ip,
            'host_name': host,
        }
        with mock.patch.object(mocked_resource.client, "get",
                               return_value=body):
            mocked_resource.update_binding(server_uuid,
                                           binding_uuid,
                                           mac_address=new_mac)
        body['mac_address'] = new_mac
        test_client.assert_json_call(
            'put', mocked_resource,
            'https://1.2.3.4/api/v1/%s/%s/static-bindings/%s' %
            (mocked_resource.uri_segment, server_uuid, binding_uuid),
            data=jsonutils.dumps(body, sort_keys=True),
            headers=self.default_headers())


class NodeHttpServicePropertiesTestCase(BaseTestResource):

    def setUp(self):
        super(NodeHttpServicePropertiesTestCase, self).setUp(
            resources.NodeHttpServiceProperties)

    def test_get_resource(self):
        self.skipTest("The action is not supported by this resource")

    def test_list_all(self):
        self.skipTest("The action is not supported by this resource")

    def test_delete_resource(self):
        self.skipTest("The action is not supported by this resource")

    def test_get_rate_limit(self):
        mocked_resource = self.get_mocked_resource()
        rate_limit = 40
        body = {'service_properties': {'client_api_rate_limit': rate_limit}}
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.2.0'),\
            mock.patch.object(mocked_resource.client, "url_get",
                              return_value=body):
            result = mocked_resource.get_rate_limit()
            self.assertEqual(rate_limit, result)

    def test_update_rate_limit(self):
        mocked_resource = self.get_mocked_resource()
        old_rate_limit = 40
        new_rate_limit = 50
        body = {'service_properties': {
                'client_api_rate_limit': old_rate_limit}}
        with mock.patch("vmware_nsxlib.v3.NsxLib.get_version",
                        return_value='2.2.0'),\
            mock.patch.object(mocked_resource.client, "url_get",
                              return_value=body):
            mocked_resource.update_rate_limit(new_rate_limit)
            body['service_properties'][
                'client_api_rate_limit'] = new_rate_limit
            test_client.assert_json_call(
                'put', mocked_resource,
                'https://1.2.3.4/api/v1/node/services/http',
                data=jsonutils.dumps(body, sort_keys=True),
                headers=self.default_headers())
            test_client.assert_json_call(
                'post', mocked_resource,
                'https://1.2.3.4/api/v1/node/services/http?action=restart',
                headers=self.default_headers())


class DummyCachedResource(utils.NsxLibApiBase):

    @property
    def uri_segment(self):
        return 'XXX'

    @property
    def resource_type(self):
        return 'xxx'

    @property
    def use_cache_for_get(self):
        return True

    @property
    def cache_timeout(self):
        return 2


class ResourceCache(BaseTestResource):

    def setUp(self):
        super(ResourceCache, self).setUp(DummyCachedResource)

    def test_get_with_cache(self):
        mocked_resource = self.get_mocked_resource()
        fake_uuid = uuidutils.generate_uuid()
        # first call -> goes to the client
        mocked_resource.get(fake_uuid)
        self.assertEqual(1, test_client.mock_calls_count(
            'get', mocked_resource))

        # second call -> goes to cache
        mocked_resource.get(fake_uuid)
        self.assertEqual(1, test_client.mock_calls_count(
            'get', mocked_resource))

        # a different call -> goes to the client
        fake_uuid2 = uuidutils.generate_uuid()
        mocked_resource.get(fake_uuid2)
        self.assertEqual(2, test_client.mock_calls_count(
            'get', mocked_resource))

        # third call -> still goes to cache
        mocked_resource.get(fake_uuid)
        self.assertEqual(2, test_client.mock_calls_count(
            'get', mocked_resource))

        # after timeout -> goes to the client
        eventlet.sleep(2)
        mocked_resource.get(fake_uuid)
        self.assertEqual(3, test_client.mock_calls_count(
            'get', mocked_resource))

        # after delete -> goes to the client
        mocked_resource.delete(fake_uuid)
        mocked_resource.get(fake_uuid)
        self.assertEqual(4, test_client.mock_calls_count(
            'get', mocked_resource))

        # And from cache again
        mocked_resource.get(fake_uuid)
        self.assertEqual(4, test_client.mock_calls_count(
            'get', mocked_resource))

        # Update the entry. The get inside the update is from
        # the client too, because it must be current)
        mocked_resource._update_with_retry(fake_uuid, {})
        self.assertEqual(5, test_client.mock_calls_count(
            'get', mocked_resource))

        # after update -> goes to client
        mocked_resource.get(fake_uuid)
        self.assertEqual(6, test_client.mock_calls_count(
            'get', mocked_resource))
