# -*- coding: utf-8 -*-
"""
Tests for salt.states.zpool

:codeauthor:    Jorge Schrauwen <sjorge@blackdot.be>
:maintainer:    Jorge Schrauwen <sjorge@blackdot.be>
:maturity:      new
:depends:       salt.utils.zfs, salt.modules.zpool
:platform:      illumos,freebsd,linux
"""
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals

# Import Salt Utils
import salt.loader
import salt.states.zpool as zpool

# Import Salt Execution module to test
import salt.utils.zfs
from salt.utils.odict import OrderedDict

# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, patch
from tests.support.unit import TestCase

# Import test data from salt.utils.zfs test
from tests.unit.utils.test_zfs import ZFSMockData

utils_patch = ZFSMockData().get_patched_utils()


class ZpoolTestCase(TestCase, LoaderModuleMockMixin):
    """
    Test cases for salt.states.zpool
    """

    def setup_loader_modules(self):
        self.opts = opts = salt.config.DEFAULT_MINION_OPTS.copy()
        utils = salt.loader.utils(opts, whitelist=["zfs"])
        zpool_obj = {
            zpool: {
                "__opts__": opts,
                "__grains__": {"kernel": "SunOS"},
                "__utils__": utils,
            }
        }

        return zpool_obj

    def test_absent_without_pool(self):
        """
        Test zpool absent without a pool
        """
        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "storage pool myzpool is absent",
            "changes": {},
        }

        mock_exists = MagicMock(return_value=False)
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__utils__, utils_patch
        ):
            self.assertEqual(zpool.absent("myzpool"), ret)

    def test_absent_destroy_pool(self):
        """
        Test zpool absent destroying pool
        """
        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "storage pool myzpool was destroyed",
            "changes": {"myzpool": "destroyed"},
        }

        mock_exists = MagicMock(return_value=True)
        mock_destroy = MagicMock(return_value=OrderedDict([("destroyed", True)]))
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.destroy": mock_destroy}
        ), patch.dict(zpool.__utils__, utils_patch):
            self.assertEqual(zpool.absent("myzpool"), ret)

    def test_absent_exporty_pool(self):
        """
        Test zpool absent exporting pool
        """
        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "storage pool myzpool was exported",
            "changes": {"myzpool": "exported"},
        }

        mock_exists = MagicMock(return_value=True)
        mock_destroy = MagicMock(return_value=OrderedDict([("exported", True)]))
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.export": mock_destroy}
        ), patch.dict(zpool.__utils__, utils_patch):
            self.assertEqual(zpool.absent("myzpool", export=True), ret)

    def test_absent_busy(self):
        """
        Test zpool absent on a busy pool
        """
        ret = {
            "name": "myzpool",
            "result": False,
            "comment": "\n".join(
                [
                    "cannot unmount '/myzpool': Device busy",
                    "cannot export 'myzpool': pool is busy",
                ]
            ),
            "changes": {},
        }

        mock_exists = MagicMock(return_value=True)
        mock_destroy = MagicMock(
            return_value=OrderedDict(
                [
                    ("exported", False),
                    (
                        "error",
                        "\n".join(
                            [
                                "cannot unmount '/myzpool': Device busy",
                                "cannot export 'myzpool': pool is busy",
                            ]
                        ),
                    ),
                ]
            )
        )
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.export": mock_destroy}
        ), patch.dict(zpool.__utils__, utils_patch):
            self.assertEqual(zpool.absent("myzpool", export=True), ret)

    def test_present_import_success(self):
        """
        Test zpool present with import allowed and unimported pool
        """
        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "storage pool myzpool was imported",
            "changes": {"myzpool": "imported"},
        }

        config = {
            "import": True,
        }

        mock_exists = MagicMock(return_value=False)
        mock_import = MagicMock(return_value=OrderedDict([("imported", True)]))
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.import": mock_import}
        ), patch.dict(zpool.__utils__, utils_patch):
            self.assertEqual(zpool.present("myzpool", config=config), ret)

    def test_present_import_fail(self):
        """
        Test zpool present with import allowed and no unimported pool or layout
        """
        ret = {
            "name": "myzpool",
            "result": False,
            "comment": "storage pool myzpool was not imported, no (valid) layout specified for creation",
            "changes": {},
        }

        config = {
            "import": True,
        }

        mock_exists = MagicMock(return_value=False)
        mock_import = MagicMock(return_value=OrderedDict([("imported", False)]))
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.import": mock_import}
        ), patch.dict(zpool.__utils__, utils_patch):
            self.assertEqual(zpool.present("myzpool", config=config), ret)

    def test_present_create_success(self):
        """
        Test zpool present with non existing pool
        """
        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "storage pool myzpool was created",
            "changes": {"myzpool": "created"},
        }

        config = {
            "import": False,
        }
        layout = [
            OrderedDict([("mirror", ["disk0", "disk1"])]),
            OrderedDict([("mirror", ["disk2", "disk3"])]),
        ]
        properties = {
            "autoexpand": True,
        }
        filesystem_properties = {
            "quota": "5G",
        }

        mock_exists = MagicMock(return_value=False)
        mock_create = MagicMock(
            return_value=OrderedDict(
                [
                    ("created", True),
                    (
                        "vdevs",
                        OrderedDict(
                            [
                                ("mirror-0", ["/dev/dsk/disk0", "/dev/dsk/disk1"]),
                                ("mirror-1", ["/dev/dsk/disk2", "/dev/dsk/disk3"]),
                            ]
                        ),
                    ),
                ]
            )
        )
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.create": mock_create}
        ), patch.dict(zpool.__utils__, utils_patch):
            self.assertEqual(
                zpool.present(
                    "myzpool",
                    config=config,
                    layout=layout,
                    properties=properties,
                    filesystem_properties=filesystem_properties,
                ),
                ret,
            )

    def test_present_create_fail(self):
        """
        Test zpool present with non existing pool (without a layout)
        """
        ret = {
            "name": "myzpool",
            "result": False,
            "comment": "storage pool myzpool was not imported, no (valid) layout specified for creation",
            "changes": {},
        }

        config = {
            "import": False,
        }

        mock_exists = MagicMock(return_value=False)
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__utils__, utils_patch
        ):
            self.assertEqual(zpool.present("myzpool", config=config), ret)

    def test_present_create_passthrough_fail(self):
        """
        Test zpool present with non existing pool (without a layout)
        """
        ret = {
            "name": "myzpool",
            "result": False,
            "comment": "\n".join(
                [
                    "invalid vdev specification",
                    "use 'force=True' to override the following errors:",
                    "/data/salt/vdisk0 is part of exported pool 'zsalt'",
                    "/data/salt/vdisk1 is part of exported pool 'zsalt'",
                ]
            ),
            "changes": {},
        }

        config = {
            "force": False,
            "import": False,
        }
        layout = [
            OrderedDict([("mirror", ["disk0", "disk1"])]),
            OrderedDict([("mirror", ["disk2", "disk3"])]),
        ]
        properties = {
            "autoexpand": True,
        }
        filesystem_properties = {
            "quota": "5G",
        }

        mock_exists = MagicMock(return_value=False)
        mock_create = MagicMock(
            return_value=OrderedDict(
                [
                    ("created", False),
                    (
                        "error",
                        "\n".join(
                            [
                                "invalid vdev specification",
                                "use 'force=True' to override the following errors:",
                                "/data/salt/vdisk0 is part of exported pool 'zsalt'",
                                "/data/salt/vdisk1 is part of exported pool 'zsalt'",
                            ]
                        ),
                    ),
                ]
            )
        )
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.create": mock_create}
        ), patch.dict(zpool.__utils__, utils_patch):
            self.assertEqual(
                zpool.present(
                    "myzpool",
                    config=config,
                    layout=layout,
                    properties=properties,
                    filesystem_properties=filesystem_properties,
                ),
                ret,
            )

    def test_present_update_success(self):
        """
        Test zpool present with an existing pool that needs an update
        """
        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "properties updated",
            "changes": {"myzpool": {"autoexpand": False}},
        }

        config = {
            "import": False,
        }
        layout = [
            OrderedDict([("mirror", ["disk0", "disk1"])]),
            OrderedDict([("mirror", ["disk2", "disk3"])]),
        ]
        properties = {
            "autoexpand": False,
        }

        mock_exists = MagicMock(return_value=True)
        mock_get = MagicMock(
            return_value=OrderedDict(
                [
                    ("comment", "salt managed pool"),
                    ("freeing", 0),
                    ("listsnapshots", False),
                    ("leaked", 0),
                    ("feature@obsolete_counts", "enabled"),
                    ("feature@sha512", "enabled"),
                    ("delegation", True),
                    ("dedupditto", "0"),
                    ("dedupratio", "1.00x"),
                    ("autoexpand", True),
                    ("feature@bookmarks", "enabled"),
                    ("allocated", 115712),
                    ("guid", 1591906802560842214),
                    ("feature@large_blocks", "enabled"),
                    ("size", 2113929216),
                    ("feature@enabled_txg", "active"),
                    ("feature@hole_birth", "active"),
                    ("capacity", 0),
                    ("feature@multi_vdev_crash_dump", "enabled"),
                    ("feature@extensible_dataset", "enabled"),
                    ("cachefile", "-"),
                    ("bootfs", "-"),
                    ("autoreplace", True),
                    ("readonly", False),
                    ("version", "-"),
                    ("health", "ONLINE"),
                    ("expandsize", "-"),
                    ("feature@embedded_data", "active"),
                    ("feature@lz4_compress", "active"),
                    ("feature@async_destroy", "enabled"),
                    ("feature@skein", "enabled"),
                    ("feature@empty_bpobj", "enabled"),
                    ("feature@spacemap_histogram", "active"),
                    ("bootsize", "-"),
                    ("free", 2113813504),
                    ("feature@device_removal", "enabled"),
                    ("failmode", "wait"),
                    ("feature@filesystem_limits", "enabled"),
                    ("feature@edonr", "enabled"),
                    ("altroot", "-"),
                    ("fragmentation", "0%"),
                ]
            )
        )
        mock_set = MagicMock(return_value=OrderedDict([("set", True)]))
        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}), patch.dict(
            zpool.__salt__, {"zpool.get": mock_get}
        ), patch.dict(zpool.__salt__, {"zpool.set": mock_set}), patch.dict(
            zpool.__utils__, utils_patch
        ):
            self.assertEqual(
                zpool.present(
                    "myzpool", config=config, layout=layout, properties=properties,
                ),
                ret,
            )

    def test_present_update_nochange_success(self):
        """
        Test zpool present with non existing pool
        """
        config = {
            "import": False,
        }
        layout = [
            OrderedDict([("mirror", ["disk0", "disk1"])]),
            OrderedDict([("mirror", ["disk2", "disk3"])]),
        ]
        properties = {
            "autoexpand": True,
        }

        mock_exists = MagicMock(return_value=True)
        mock_get = MagicMock(
            return_value=OrderedDict(
                [
                    ("comment", "salt managed pool"),
                    ("freeing", 0),
                    ("listsnapshots", False),
                    ("leaked", 0),
                    ("feature@obsolete_counts", "enabled"),
                    ("feature@sha512", "enabled"),
                    ("delegation", True),
                    ("dedupditto", "0"),
                    ("dedupratio", "1.00x"),
                    ("autoexpand", True),
                    ("feature@bookmarks", "enabled"),
                    ("allocated", 115712),
                    ("guid", 1591906802560842214),
                    ("feature@large_blocks", "enabled"),
                    ("size", 2113929216),
                    ("feature@enabled_txg", "active"),
                    ("feature@hole_birth", "active"),
                    ("capacity", 0),
                    ("feature@multi_vdev_crash_dump", "enabled"),
                    ("feature@extensible_dataset", "enabled"),
                    ("cachefile", "-"),
                    ("bootfs", "-"),
                    ("autoreplace", True),
                    ("readonly", False),
                    ("version", "-"),
                    ("health", "ONLINE"),
                    ("expandsize", "-"),
                    ("feature@embedded_data", "active"),
                    ("feature@lz4_compress", "active"),
                    ("feature@async_destroy", "enabled"),
                    ("feature@skein", "enabled"),
                    ("feature@empty_bpobj", "enabled"),
                    ("feature@spacemap_histogram", "active"),
                    ("bootsize", "-"),
                    ("free", 2113813504),
                    ("feature@device_removal", "enabled"),
                    ("failmode", "wait"),
                    ("feature@filesystem_limits", "enabled"),
                    ("feature@edonr", "enabled"),
                    ("altroot", "-"),
                    ("fragmentation", "0%"),
                ]
            )
        )

        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "no update needed",
            "changes": {},
        }

        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}):
            with patch.dict(zpool.__salt__, {"zpool.get": mock_get}):
                with patch.dict(zpool.__utils__, utils_patch):
                    self.assertEqual(
                        zpool.present(
                            "myzpool",
                            config=config,
                            layout=layout,
                            properties=properties,
                        ),
                        ret,
                    )

        # Run state with test=true
        ret = {
            "name": "myzpool",
            "result": True,
            "comment": "storage pool myzpool is uptodate",
            "changes": {},
        }

        with patch.dict(zpool.__salt__, {"zpool.exists": mock_exists}):
            with patch.dict(zpool.__salt__, {"zpool.get": mock_get}):
                with patch.dict(zpool.__utils__, utils_patch):
                    with patch.dict(zpool.__opts__, {"test": True}):
                        self.assertEqual(
                            zpool.present(
                                "myzpool",
                                config=config,
                                layout=layout,
                                properties=properties,
                            ),
                            ret,
                        )
