#    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 json
from unittest import mock

from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.targets import spdknvmf as spdknvmf_driver


BDEVS = [{
    "num_blocks": 4096000,
    "name": "Nvme0n1",
    "driver_specific": {
        "nvme": {
            "trid": {
                "trtype": "PCIe",
                "traddr": "0000:00:04.0"
            },
            "ns_data": {
                "id": 1
            },
            "pci_address": "0000:00:04.0",
            "vs": {
                "nvme_version": "1.1"
            },
            "ctrlr_data": {
                "firmware_revision": "1.0",
                "serial_number": "deadbeef",
                "oacs": {
                    "ns_manage": 0,
                    "security": 0,
                    "firmware": 0,
                    "format": 0
                },
                "vendor_id": "0x8086",
                "model_number": "QEMU NVMe Ctrl"
            },
            "csts": {
                "rdy": 1,
                "cfs": 0
            }
        }
    },
    "supported_io_types": {
        "reset": True,
        "nvme_admin": True,
        "unmap": False,
        "read": True,
        "write_zeroes": False,
        "write": True,
        "flush": True,
        "nvme_io": True
    },
    "claimed": False,
    "block_size": 512,
    "product_name": "NVMe disk",
    "aliases": ["Nvme0n1"]
}, {
    "num_blocks": 8192,
    "uuid": "70efd305-4e66-49bd-99ff-faeda5c3052d",
    "aliases": [
        "Nvme0n1p0"
    ],
    "driver_specific": {
        "lvol": {
            "base_bdev": "Nvme0n1",
            "lvol_store_uuid": "58b17014-d4a1-4f85-9761-093643ed18f1",
            "thin_provision": False
        }
    },
    "supported_io_types": {
        "reset": True,
        "nvme_admin": False,
        "unmap": True,
        "read": True,
        "write_zeroes": True,
        "write": True,
        "flush": False,
        "nvme_io": False
    },
    "claimed": False,
    "block_size": 4096,
    "product_name": "Split Disk",
    "name": "Nvme0n1p0"
}, {
    "num_blocks": 8192,
    "uuid": "70efd305-4e66-49bd-99ff-faeda5c3052d",
    "aliases": [
        "Nvme0n1p1"
    ],
    "driver_specific": {
        "lvol": {
            "base_bdev": "Nvme0n1",
            "lvol_store_uuid": "58b17014-d4a1-4f85-9761-093643ed18f1",
            "thin_provision": False
        }
    },
    "supported_io_types": {
        "reset": True,
        "nvme_admin": False,
        "unmap": True,
        "read": True,
        "write_zeroes": True,
        "write": True,
        "flush": False,
        "nvme_io": False
    },
    "claimed": False,
    "block_size": 4096,
    "product_name": "Split Disk",
    "name": "Nvme0n1p1"
}, {
    "num_blocks": 8192,
    "uuid": "70efd305-4e66-49bd-99ff-faeda5c3052d",
    "aliases": [
        "lvs_test/lvol0"
    ],
    "driver_specific": {
        "lvol": {
            "base_bdev": "Malloc0",
            "lvol_store_uuid": "58b17014-d4a1-4f85-9761-093643ed18f1",
            "thin_provision": False
        }
    },
    "supported_io_types": {
        "reset": True,
        "nvme_admin": False,
        "unmap": True,
        "read": True,
        "write_zeroes": True,
        "write": True,
        "flush": False,
        "nvme_io": False
    },
    "claimed": False,
    "block_size": 4096,
    "product_name": "Logical Volume",
    "name": "58b17014-d4a1-4f85-9761-093643ed18f1_4294967297"
}, {
    "num_blocks": 8192,
    "uuid": "8dec1964-d533-41df-bea7-40520efdb416",
    "aliases": [
        "lvs_test/lvol1"
    ],
    "driver_specific": {
        "lvol": {
            "base_bdev": "Malloc0",
            "lvol_store_uuid": "58b17014-d4a1-4f85-9761-093643ed18f1",
            "thin_provision": True
        }
    },
    "supported_io_types": {
        "reset": True,
        "nvme_admin": False,
        "unmap": True,
        "read": True,
        "write_zeroes": True,
        "write": True,
        "flush": False,
        "nvme_io": False
    },
    "claimed": False,
    "block_size": 4096,
    "product_name": "Logical Volume",
    "name": "58b17014-d4a1-4f85-9761-093643ed18f1_4294967298"
}]


NVMF_SUBSYSTEMS = [{
    "listen_addresses": [],
    "subtype": "Discovery",
    "nqn": "nqn.2014-08.org.nvmexpress.discovery",
    "hosts": [],
    "allow_any_host": True
}, {
    "listen_addresses": [],
    "subtype": "NVMe",
    "hosts": [{
        "nqn": "nqn.2016-06.io.spdk:init"
    }],
    "namespaces": [{
        "bdev_name": "Nvme0n1p0",
        "nsid": 1,
        "name": "Nvme0n1p0"
    }],
    "allow_any_host": False,
    "serial_number": "SPDK00000000000001",
    "nqn": "nqn.2016-06.io.spdk:cnode1"
}, {
    "listen_addresses": [],
    "subtype": "NVMe",
    "hosts": [],
    "namespaces": [{
        "bdev_name": "Nvme1n1p0",
        "nsid": 1,
        "name": "Nvme1n1p0"
    }],
    "allow_any_host": True,
    "serial_number": "SPDK00000000000002",
    "nqn": "nqn.2016-06.io.spdk:cnode2"
}]


class JSONRPCException(Exception):
    def __init__(self, message):
        self.message = message


class JSONRPCClient(object):
    def __init__(self, addr=None, port=None):
        self.methods = {"bdev_get_bdevs": self.get_bdevs,
                        "construct_nvmf_subsystem":
                            self.construct_nvmf_subsystem,
                        "nvmf_delete_subsystem": self.delete_nvmf_subsystem,
                        "nvmf_create_subsystem": self.nvmf_subsystem_create,
                        "nvmf_subsystem_add_listener":
                            self.nvmf_subsystem_add_listener,
                        "nvmf_subsystem_add_ns":
                            self.nvmf_subsystem_add_ns,
                        "nvmf_get_subsystems": self.get_nvmf_subsystems}
        self.bdevs = copy.deepcopy(BDEVS)
        self.nvmf_subsystems = copy.deepcopy(NVMF_SUBSYSTEMS)

    def __del__(self):
        pass

    def get_bdevs(self, params=None):
        if params and 'name' in params:
            for bdev in self.bdevs:
                for alias in bdev['aliases']:
                    if params['name'] in alias:
                        return json.dumps({"result": [bdev]})
                if bdev['name'] == params['name']:
                    return json.dumps({"result": [bdev]})
            return json.dumps({"error": "Not found"})

        return json.dumps({"result": self.bdevs})

    def get_nvmf_subsystems(self, params=None):
        return json.dumps({"result": self.nvmf_subsystems})

    def construct_nvmf_subsystem(self, params=None):
        nvmf_subsystem = {
            "listen_addresses": [],
            "subtype": "NVMe",
            "hosts": [],
            "namespaces": [{
                "bdev_name": "Nvme1n1p0",
                "nsid": 1,
                "name": "Nvme1n1p0"
            }],
            "allow_any_host": True,
            "serial_number": params['serial_number'],
            "nqn": params['nqn']
        }
        self.nvmf_subsystems.append(nvmf_subsystem)

        return json.dumps({"result": nvmf_subsystem})

    def delete_nvmf_subsystem(self, params=None):
        found_id = -1
        i = 0
        for nvmf_subsystem in self.nvmf_subsystems:
            if nvmf_subsystem['nqn'] == params['nqn']:
                found_id = i
            i += 1

        if found_id != -1:
            del self.nvmf_subsystems[found_id]

        return json.dumps({"result": {}})

    def nvmf_subsystem_create(self, params=None):
        nvmf_subsystem = {
            "namespaces": [],
            "nqn": params['nqn'],
            "serial_number": "S0000000000000000001",
            "allow_any_host": False,
            "subtype": "NVMe",
            "hosts": [],
            "listen_addresses": []
        }

        self.nvmf_subsystems.append(nvmf_subsystem)

        return json.dumps({"result": nvmf_subsystem})

    def nvmf_subsystem_add_listener(self, params=None):
        for nvmf_subsystem in self.nvmf_subsystems:
            if nvmf_subsystem['nqn'] == params['nqn']:
                nvmf_subsystem['listen_addresses'].append(
                    params['listen_address']
                )

        return json.dumps({"result": ""})

    def nvmf_subsystem_add_ns(self, params=None):
        for nvmf_subsystem in self.nvmf_subsystems:
            if nvmf_subsystem['nqn'] == params['nqn']:
                nvmf_subsystem['namespaces'].append(
                    params['namespace']
                )

        return json.dumps({"result": ""})

    def call(self, method, params=None):
        req = {}
        req['jsonrpc'] = '2.0'
        req['method'] = method
        req['id'] = 1
        if (params):
            req['params'] = params
        response = json.loads(self.methods[method](params))
        if not response:
            return {}

        if 'error' in response:
            msg = "\n".join(["Got JSON-RPC error response",
                             "request:",
                             json.dumps(req, indent=2),
                             "response:",
                             json.dumps(response['error'], indent=2)])
            raise JSONRPCException(msg)

        return response['result']


class Target(object):
    def __init__(self, name="Nvme0n1p0"):
        self.name = name


class SpdkNvmfDriverTestCase(test.TestCase):
    def setUp(self):
        super(SpdkNvmfDriverTestCase, self).setUp()
        self.configuration = mock.Mock(conf.Configuration)
        self.configuration.target_ip_address = '192.168.0.1'
        self.configuration.target_port = '4420'
        self.configuration.target_prefix = ""
        self.configuration.nvmet_port_id = "1"
        self.configuration.nvmet_ns_id = "fake_id"
        self.configuration.nvmet_subsystem_name = "nqn.2014-08.io.spdk"
        self.configuration.target_protocol = "nvmet_rdma"
        self.configuration.spdk_rpc_ip = "127.0.0.1"
        self.configuration.spdk_rpc_port = 8000
        self.driver = spdknvmf_driver.SpdkNvmf(configuration=
                                               self.configuration)
        self.jsonrpcclient = JSONRPCClient()

    def test__get_spdk_volume_name(self):
        with mock.patch.object(self.driver, "_rpc_call",
                               self.jsonrpcclient.call):
            bdevs = self.driver._rpc_call("bdev_get_bdevs")
            bdev_name = bdevs[0]['name']
            volume_name = self.driver._get_spdk_volume_name(bdev_name)
            self.assertEqual(bdev_name, volume_name)
            volume_name = self.driver._get_spdk_volume_name("fake")
            self.assertIsNone(volume_name)

    def test__get_nqn_with_volume_name(self):
        with mock.patch.object(self.driver, "_rpc_call",
                               self.jsonrpcclient.call):
            nqn = self.driver._get_nqn_with_volume_name("Nvme0n1p0")
            nqn_tmp = self.driver._rpc_call("nvmf_get_subsystems")[1]['nqn']
            self.assertEqual(nqn, nqn_tmp)
            nqn = self.driver._get_nqn_with_volume_name("fake")
            self.assertIsNone(nqn)

    def test__get_first_free_node(self):
        with mock.patch.object(self.driver, "_rpc_call",
                               self.jsonrpcclient.call):
            free_node = self.driver._get_first_free_node()
            self.assertEqual(3, free_node)

    def test_create_nvmeof_target(self):
        with mock.patch.object(self.driver, "_rpc_call",
                               self.jsonrpcclient.call):
            subsystems_first = self.driver._rpc_call("nvmf_get_subsystems")
            self.driver.create_nvmeof_target("Nvme0n1p1",
                                             "nqn.2016-06.io.spdk",
                                             "192.168.0.1",
                                             4420, "rdma", -1, -1, "")
            subsystems_last = self.driver._rpc_call("nvmf_get_subsystems")
            self.assertEqual(len(subsystems_first) + 1, len(subsystems_last))

    def test_delete_nvmeof_target(self):
        with mock.patch.object(self.driver, "_rpc_call",
                               self.jsonrpcclient.call):
            subsystems_first = self.driver._rpc_call("nvmf_get_subsystems")
            target = Target()
            self.driver.delete_nvmeof_target(target)
            subsystems_last = self.driver._rpc_call("nvmf_get_subsystems")
            self.assertEqual(len(subsystems_first) - 1, len(subsystems_last))
            target.name = "fake"
            self.driver.delete_nvmeof_target(target)
            self.assertEqual(len(subsystems_first) - 1, len(subsystems_last))

[((233, 21, 233, 41), 'copy.deepcopy', 'copy.deepcopy', ({(233, 35, 233, 40): 'BDEVS'}, {}), '(BDEVS)', False, 'import copy\n'), ((234, 31, 234, 61), 'copy.deepcopy', 'copy.deepcopy', ({(234, 45, 234, 60): 'NVMF_SUBSYSTEMS'}, {}), '(NVMF_SUBSYSTEMS)', False, 'import copy\n'), ((249, 15, 249, 49), 'json.dumps', 'json.dumps', ({(249, 26, 249, 48): "{'result': self.bdevs}"}, {}), "({'result': self.bdevs})", False, 'import json\n'), ((252, 15, 252, 59), 'json.dumps', 'json.dumps', ({(252, 26, 252, 58): "{'result': self.nvmf_subsystems}"}, {}), "({'result': self.nvmf_subsystems})", False, 'import json\n'), ((270, 15, 270, 53), 'json.dumps', 'json.dumps', ({(270, 26, 270, 52): "{'result': nvmf_subsystem}"}, {}), "({'result': nvmf_subsystem})", False, 'import json\n'), ((283, 15, 283, 41), 'json.dumps', 'json.dumps', ({(283, 26, 283, 40): "{'result': {}}"}, {}), "({'result': {}})", False, 'import json\n'), ((298, 15, 298, 53), 'json.dumps', 'json.dumps', ({(298, 26, 298, 52): "{'result': nvmf_subsystem}"}, {}), "({'result': nvmf_subsystem})", False, 'import json\n'), ((307, 15, 307, 41), 'json.dumps', 'json.dumps', ({(307, 26, 307, 40): "{'result': ''}"}, {}), "({'result': ''})", False, 'import json\n'), ((316, 15, 316, 41), 'json.dumps', 'json.dumps', ({(316, 26, 316, 40): "{'result': ''}"}, {}), "({'result': ''})", False, 'import json\n'), ((348, 29, 348, 58), 'unittest.mock.Mock', 'mock.Mock', ({(348, 39, 348, 57): 'conf.Configuration'}, {}), '(conf.Configuration)', False, 'from unittest import mock\n'), ((358, 22, 359, 66), 'cinder.volume.targets.spdknvmf.SpdkNvmf', 'spdknvmf_driver.SpdkNvmf', (), '', True, 'from cinder.volume.targets import spdknvmf as spdknvmf_driver\n'), ((247, 19, 247, 53), 'json.dumps', 'json.dumps', ({(247, 30, 247, 52): "{'error': 'Not found'}"}, {}), "({'error': 'Not found'})", False, 'import json\n'), ((363, 13, 364, 55), 'unittest.mock.patch.object', 'mock.patch.object', ({(363, 31, 363, 42): 'self.driver', (363, 44, 363, 55): '"""_rpc_call"""', (364, 31, 364, 54): 'self.jsonrpcclient.call'}, {}), "(self.driver, '_rpc_call', self.jsonrpcclient.call)", False, 'from unittest import mock\n'), ((373, 13, 374, 55), 'unittest.mock.patch.object', 'mock.patch.object', ({(373, 31, 373, 42): 'self.driver', (373, 44, 373, 55): '"""_rpc_call"""', (374, 31, 374, 54): 'self.jsonrpcclient.call'}, {}), "(self.driver, '_rpc_call', self.jsonrpcclient.call)", False, 'from unittest import mock\n'), ((382, 13, 383, 55), 'unittest.mock.patch.object', 'mock.patch.object', ({(382, 31, 382, 42): 'self.driver', (382, 44, 382, 55): '"""_rpc_call"""', (383, 31, 383, 54): 'self.jsonrpcclient.call'}, {}), "(self.driver, '_rpc_call', self.jsonrpcclient.call)", False, 'from unittest import mock\n'), ((388, 13, 389, 55), 'unittest.mock.patch.object', 'mock.patch.object', ({(388, 31, 388, 42): 'self.driver', (388, 44, 388, 55): '"""_rpc_call"""', (389, 31, 389, 54): 'self.jsonrpcclient.call'}, {}), "(self.driver, '_rpc_call', self.jsonrpcclient.call)", False, 'from unittest import mock\n'), ((399, 13, 400, 55), 'unittest.mock.patch.object', 'mock.patch.object', ({(399, 31, 399, 42): 'self.driver', (399, 44, 399, 55): '"""_rpc_call"""', (400, 31, 400, 54): 'self.jsonrpcclient.call'}, {}), "(self.driver, '_rpc_call', self.jsonrpcclient.call)", False, 'from unittest import mock\n'), ((246, 27, 246, 57), 'json.dumps', 'json.dumps', ({(246, 38, 246, 56): "{'result': [bdev]}"}, {}), "({'result': [bdev]})", False, 'import json\n'), ((332, 29, 332, 54), 'json.dumps', 'json.dumps', (), '', False, 'import json\n'), ((334, 29, 334, 68), 'json.dumps', 'json.dumps', (), '', False, 'import json\n'), ((244, 31, 244, 61), 'json.dumps', 'json.dumps', ({(244, 42, 244, 60): "{'result': [bdev]}"}, {}), "({'result': [bdev]})", False, 'import json\n')]