#    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, 'copy.deepcopy', 'copy.deepcopy', False, 'import copy\n'), (234, 'copy.deepcopy', 'copy.deepcopy', False, 'import copy\n'), (249, 'json.dumps', 'json.dumps', False, 'import json\n'), (252, 'json.dumps', 'json.dumps', False, 'import json\n'), (270, 'json.dumps', 'json.dumps', False, 'import json\n'), (283, 'json.dumps', 'json.dumps', False, 'import json\n'), (298, 'json.dumps', 'json.dumps', False, 'import json\n'), (307, 'json.dumps', 'json.dumps', False, 'import json\n'), (316, 'json.dumps', 'json.dumps', False, 'import json\n'), (348, 'unittest.mock.Mock', 'mock.Mock', False, 'from unittest import mock\n'), (358, 'cinder.volume.targets.spdknvmf.SpdkNvmf', 'spdknvmf_driver.SpdkNvmf', True, 'from cinder.volume.targets import spdknvmf as spdknvmf_driver\n'), (247, 'json.dumps', 'json.dumps', False, 'import json\n'), (363, 'unittest.mock.patch.object', 'mock.patch.object', False, 'from unittest import mock\n'), (373, 'unittest.mock.patch.object', 'mock.patch.object', False, 'from unittest import mock\n'), (382, 'unittest.mock.patch.object', 'mock.patch.object', False, 'from unittest import mock\n'), (388, 'unittest.mock.patch.object', 'mock.patch.object', False, 'from unittest import mock\n'), (399, 'unittest.mock.patch.object', 'mock.patch.object', False, 'from unittest import mock\n'), (246, 'json.dumps', 'json.dumps', False, 'import json\n'), (332, 'json.dumps', 'json.dumps', False, 'import json\n'), (334, 'json.dumps', 'json.dumps', False, 'import json\n'), (244, 'json.dumps', 'json.dumps', False, 'import json\n')]