#    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 time

import ddt
from oslo_log import log as logging
from oslo_utils import units
from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions
from testtools import testcase as tc

from manila_tempest_tests.common import constants
from manila_tempest_tests.common import waiters
from manila_tempest_tests.tests.api import base
from manila_tempest_tests.tests.scenario import manager_share as manager


CONF = config.CONF
LOG = logging.getLogger(__name__)


@ddt.ddt
class ShareExtendBase(manager.ShareScenarioTest):

    """This test case uses the following flow:

     * Launch an instance
     * Create share (1GB)
     * Configure RW access to the share
     * Perform ssh to instance
     * Mount share
     * Write data in share
     * Extend share (2GB)
     * Write more data in share
     * Unmount share
     * Delete share
     * Terminate the instance
    """

    @decorators.idempotent_id('e1c0d614-c8f2-43cf-9c49-25808b07ba4a')
    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
    def test_create_extend_and_write(self):
        default_share_size = CONF.share.share_size
        additional_overflow_blocks = CONF.share.additional_overflow_blocks
        share_growth_size = CONF.share.share_growth_size

        LOG.debug('Step 1 - create instance')
        instance = self.boot_instance(wait_until="BUILD")

        LOG.debug('Step 2 - create share of size {} Gb'
                  .format(default_share_size))
        share = self.create_share(size=default_share_size)

        LOG.debug('Step 3 - wait for active instance')
        instance = self.wait_for_active_instance(instance["id"])
        remote_client = self.init_remote_client(instance)

        LOG.debug('Step 4 - grant access')
        location = self.get_user_export_locations(share)[0]
        self.allow_access(share=share, instance=instance,
                          remote_client=remote_client, locations=location)

        LOG.debug('Step 5 - mount')
        self.mount_share(location, remote_client)

        total_blocks = (units.Ki * default_share_size) / 64
        three_quarter_blocks = (total_blocks / 4) * 3
        LOG.debug('Step 6 - writing {} * 64MB blocks'
                  .format(three_quarter_blocks))
        self.write_data_to_mounted_share_using_dd(remote_client,
                                                  '/mnt/t1', '64M',
                                                  three_quarter_blocks)
        time.sleep(CONF.share.share_resize_sync_delay)
        ls_result = remote_client.exec_command("sudo ls -lAh /mnt/")
        LOG.debug(ls_result)

        # Additional blocks that exceed the share capacity, defaulting to 5.
        overflow_blocks = (
            total_blocks - three_quarter_blocks +
            additional_overflow_blocks or 5)
        LOG.debug('Step 6b - Write more data, should fail')
        self.assertRaises(
            exceptions.SSHExecCommandFailed,
            self.write_data_to_mounted_share_using_dd,
            remote_client, '/mnt/t2', '64M', overflow_blocks)
        time.sleep(CONF.share.share_resize_sync_delay)
        ls_result = remote_client.exec_command("sudo ls -lAh /mnt/")
        LOG.debug(ls_result)

        LOG.debug('Step 7 - extend and wait')
        extended_share_size = default_share_size + share_growth_size
        self.shares_v2_client.extend_share(share["id"],
                                           new_size=extended_share_size)
        waiters.wait_for_resource_status(
            self.shares_v2_client, share["id"], constants.STATUS_AVAILABLE)
        share = self.shares_v2_client.get_share(share["id"])['share']
        self.assertEqual(extended_share_size, int(share["size"]))
        time.sleep(CONF.share.share_resize_sync_delay)

        LOG.debug('Step 8 - writing more data, should succeed')
        self.write_data_with_remount(location, remote_client, '/mnt/t3',
                                     '64M', overflow_blocks)
        ls_result = remote_client.exec_command("sudo ls -lAh /mnt/")
        LOG.debug(ls_result)

        LOG.debug('Step 9 - unmount')
        self.unmount_share(remote_client)

    def write_data_with_remount(self, mount_location,
                                remote_client,
                                output_file,
                                block_size,
                                block_count):
        """Writes data to mounted share using dd command

        Tries remounting once if encountering a stale file handle

        :param mount_location: Mount point for remounting if needed
        :param remote_client: An SSH client connection to the Nova instance
        :param block_size: The size of an individual block in bytes
        :param block_count: The number of blocks to write
        :param output_file: Path to the file to be written
        """

        try:
            self.write_data_to_mounted_share_using_dd(remote_client,
                                                      output_file,
                                                      block_size,
                                                      block_count)
        except exceptions.SSHExecCommandFailed as e:
            if 'stale file handle' in str(e).lower():
                LOG.warning("Client was disconnected during extend process")
                self.unmount_share(remote_client)
                self.mount_share(mount_location, remote_client)
                self.write_data_to_mounted_share_using_dd(remote_client,
                                                          output_file,
                                                          block_size,
                                                          block_count)
            else:
                raise


class TestShareExtendNFS(manager.BaseShareScenarioNFSTest, ShareExtendBase):
    pass


class TestShareExtendCIFS(manager.BaseShareScenarioCIFSTest, ShareExtendBase):

    @decorators.idempotent_id('4a9f5cf9-990d-4a35-9ac7-87ef593a09e3')
    @decorators.unstable_test(bug='1903922')
    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
    def test_create_extend_and_write(self):
        super(TestShareExtendCIFS, self).test_create_extend_and_write()


class TestBaseShareExtendScenarioCEPHFS(manager.BaseShareScenarioCEPHFSTest,
                                        ShareExtendBase):

    @decorators.idempotent_id('9ca1e4a9-23e3-4da6-a63e-46e7919335e0')
    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
    def test_create_extend_and_write_with_ceph_fuse_client(self):
        self.mount_client = 'fuse'
        super(TestBaseShareExtendScenarioCEPHFS,
              self).test_create_extend_and_write()


class TestShareExtendNFSIPv6(TestShareExtendNFS):
    ip_version = 6


# NOTE(u_glide): this function is required to exclude ShareExtendBase
# from executed test cases.
# See: https://docs.python.org/3/library/unittest.html#load-tests-protocol
# for details.
def load_tests(loader, tests, _):
    result = []
    for test_case in tests:
        if type(test_case._tests[0]) is ShareExtendBase:
            continue
        result.append(test_case)
    return loader.suiteClass(result)
