# Copyright 2013 Huawei Technologies Co.,LTD.
# 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.

from unittest import mock

import ddt

from rally_openstack.task.scenarios.cinder import volumes
from tests.unit import test

CINDER_VOLUMES = ("rally_openstack.task.scenarios.cinder.volumes")


@ddt.ddt
class CinderServersTestCase(test.ScenarioTestCase):

    def _get_context(self):
        context = test.get_test_context()
        context.update({
            "admin": {
                "id": "fake_user_id",
                "credential": mock.MagicMock()
            },
            "user": {"id": "fake_user_id",
                     "credential": mock.MagicMock()},
            "tenant": {"id": "fake", "name": "fake",
                       "volumes": [{"id": "uuid", "size": 1}],
                       "servers": [1]}})
        return context

    def setUp(self):
        super(CinderServersTestCase, self).setUp()
        patch = mock.patch(
            "rally_openstack.common.services.storage.block.BlockStorage")
        self.addCleanup(patch.stop)
        self.mock_cinder = patch.start()

    def test_create_and_list_volume(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndListVolume(self._get_context())
        scenario.run(1, True, fakearg="f")

        mock_service.create_volume.assert_called_once_with(1, fakearg="f")
        mock_service.list_volumes.assert_called_once_with(True)

    def test_create_and_get_volume(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndGetVolume(self._get_context())
        scenario.run(1, fakearg="f")
        mock_service.create_volume.assert_called_once_with(1, fakearg="f")
        mock_service.get_volume.assert_called_once_with(
            mock_service.create_volume.return_value.id)

    def test_list_volumes(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.ListVolumes(self._get_context())
        scenario.run(True)
        mock_service.list_volumes.assert_called_once_with(
            True, limit=None, marker=None, search_opts=None, sort=None)

    def test_list_types(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.ListTypes(self._get_context())
        scenario.run(None, is_public=None)
        mock_service.list_types.assert_called_once_with(None,
                                                        is_public=None)

    def test_list_transfers(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.ListTransfers(self._get_context())
        scenario._list_transfers = mock.MagicMock()
        scenario.run(True, search_opts=None)
        mock_service.list_transfers.assert_called_once_with(
            True, search_opts=None)

    @ddt.data({"update_args": {"description": "desp"},
               "expected": {"description": "desp"}},
              {"update_args": {"update_name": True, "description": "desp"},
               "expected": {"name": "new_name", "description": "desp"}})
    @ddt.unpack
    def test_create_and_update_volume(self, update_args, expected):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndUpdateVolume(self._get_context())
        scenario.generate_random_name = mock.MagicMock()
        scenario.generate_random_name.return_value = "new_name"
        scenario.run(1, update_volume_kwargs=update_args)
        mock_service.create_volume.assert_called_once_with(1)
        mock_service.update_volume.assert_called_once_with(
            mock_service.create_volume.return_value, **expected)
        if update_args.get("update_name", False):
            scenario.generate_random_name.assert_called_once_with()

    def test_create_volume_and_update_readonly_flag(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateVolumeAndUpdateReadonlyFlag(
            self._get_context())
        scenario.run(1, image=None, read_only=True, fakearg="f")
        mock_service.create_volume.assert_called_once_with(1, fakearg="f")
        mock_service.update_readonly_flag.assert_called_once_with(
            mock_service.create_volume.return_value.id, read_only=True)

    def test_create_and_delete_volume(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndDeleteVolume(self._get_context())
        scenario.sleep_between = mock.MagicMock()
        scenario.run(size=1, min_sleep=10, max_sleep=20, fakearg="f")

        mock_service.create_volume.assert_called_once_with(1, fakearg="f")
        scenario.sleep_between.assert_called_once_with(10, 20)
        mock_service.delete_volume.assert_called_once_with(
            mock_service.create_volume.return_value)

    def test_create_volume(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateVolume(self._get_context())
        scenario.run(1, fakearg="f")
        mock_service.create_volume.assert_called_once_with(1, fakearg="f")

    def test_create_volume_and_modify_metadata(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.ModifyVolumeMetadata(self._get_context())
        scenario.run(sets=5, set_size=4, deletes=3, delete_size=2)
        mock_service.set_metadata.assert_called_once_with(
            "uuid", set_size=4, sets=5)
        mock_service.delete_metadata.assert_called_once_with(
            "uuid",
            keys=mock_service.set_metadata.return_value,
            deletes=3, delete_size=2)

    def test_create_and_extend_volume(self):
        mock_service = self.mock_cinder.return_value

        scenario = volumes.CreateAndExtendVolume(self._get_context())
        scenario.sleep_between = mock.MagicMock()

        scenario.run(1, 2, 10, 20, fakearg="f")
        mock_service.create_volume.assert_called_once_with(1, fakearg="f")
        mock_service.extend_volume.assert_called_once_with(
            mock_service.create_volume.return_value, new_size=2)
        scenario.sleep_between.assert_called_once_with(10, 20)
        mock_service.delete_volume.assert_called_once_with(
            mock_service.create_volume.return_value)

    def test_create_from_image_and_delete_volume(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndDeleteVolume(self._get_context())
        scenario.run(1, image="fake_image")
        mock_service.create_volume.assert_called_once_with(
            1, imageRef="fake_image")
        mock_service.delete_volume.assert_called_once_with(
            mock_service.create_volume.return_value)

    def test_create_volume_from_image(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateVolume(self._get_context())
        scenario.run(1, image="fake_image")
        mock_service.create_volume.assert_called_once_with(
            1, imageRef="fake_image")

    def test_create_volume_from_image_and_list(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndListVolume(self._get_context())
        scenario.run(1, True, "fake_image")
        mock_service.create_volume.assert_called_once_with(
            1, imageRef="fake_image")
        mock_service.list_volumes.assert_called_once_with(True)

    def test_create_from_volume_and_delete_volume(self):
        mock_service = self.mock_cinder.return_value
        vol_size = 1
        scenario = volumes.CreateFromVolumeAndDeleteVolume(self._get_context())
        scenario.run(vol_size)
        mock_service.create_volume.assert_called_once_with(
            1, source_volid="uuid")
        mock_service.delete_volume.assert_called_once_with(
            mock_service.create_volume.return_value)

    @mock.patch("%s.CreateAndDeleteSnapshot.sleep_between" % CINDER_VOLUMES)
    def test_create_and_delete_snapshot(self, mock_sleep_between):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndDeleteSnapshot(self._get_context())
        scenario.run(False, 10, 20, fakearg="f")

        mock_service.create_snapshot.assert_called_once_with("uuid",
                                                             force=False,
                                                             fakearg="f")
        mock_sleep_between.assert_called_once_with(10, 20)
        mock_service.delete_snapshot.assert_called_once_with(
            mock_service.create_snapshot.return_value)

    def test_create_and_list_snapshots(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndListSnapshots(self._get_context())
        scenario.run(False, True, fakearg="f")
        mock_service.create_snapshot.assert_called_once_with("uuid",
                                                             force=False,
                                                             fakearg="f")
        mock_service.list_snapshots.assert_called_once_with(True)

    def test_create_and_attach_volume(self):
        fake_server = mock.MagicMock()
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndAttachVolume(self._get_context())

        scenario._boot_server = mock.MagicMock(return_value=fake_server)
        scenario._delete_server = mock.MagicMock()
        scenario._attach_volume = mock.MagicMock()
        scenario._detach_volume = mock.MagicMock()

        volume_args = {"some_key": "some_val"}
        vm_args = {"some_key": "some_val"}

        scenario.run(10, "img", "0",
                     create_volume_params=volume_args,
                     create_vm_params=vm_args)

        mock_service.create_volume.assert_called_once_with(
            10, **volume_args)
        scenario._attach_volume.assert_called_once_with(
            fake_server, mock_service.create_volume.return_value)
        scenario._detach_volume.assert_called_once_with(
            fake_server, mock_service.create_volume.return_value)

        mock_service.delete_volume.assert_called_once_with(
            mock_service.create_volume.return_value)
        scenario._delete_server.assert_called_once_with(fake_server)

    @mock.patch("rally_openstack.common.services.image.image.Image")
    def test_create_and_upload_volume_to_image(self, mock_image):
        mock_volume_service = self.mock_cinder.return_value
        mock_image_service = mock_image.return_value
        scenario = volumes.CreateAndUploadVolumeToImage(self._get_context())

        scenario.run(2, image="img", container_format="fake",
                     disk_format="disk", do_delete=False, fakeargs="fakeargs")

        mock_volume_service.create_volume.assert_called_once_with(
            2, imageRef="img", fakeargs="fakeargs")
        mock_volume_service.upload_volume_to_image.assert_called_once_with(
            mock_volume_service.create_volume.return_value,
            container_format="fake", disk_format="disk", force=False)

        mock_volume_service.create_volume.reset_mock()
        mock_volume_service.upload_volume_to_image.reset_mock()

        scenario.run(1, image=None, do_delete=True, fakeargs="fakeargs")

        mock_volume_service.create_volume.assert_called_once_with(
            1, fakeargs="fakeargs")
        mock_volume_service.upload_volume_to_image.assert_called_once_with(
            mock_volume_service.create_volume.return_value,
            container_format="bare", disk_format="raw", force=False)
        mock_volume_service.delete_volume.assert_called_once_with(
            mock_volume_service.create_volume.return_value)
        mock_image_service.delete_image.assert_called_once_with(
            mock_volume_service.upload_volume_to_image.return_value.id)

    def test_create_snapshot_and_attach_volume(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateSnapshotAndAttachVolume(self._get_context())
        scenario._boot_server = mock.MagicMock()
        scenario._attach_volume = mock.MagicMock()
        scenario._detach_volume = mock.MagicMock()
        scenario.run("img", "flavor")

        self.assertTrue(mock_service.create_volume.called)
        volume = mock_service.create_volume.return_value
        snapshot = mock_service.create_snapshot.return_value
        mock_service.create_snapshot.assert_called_once_with(volume.id,
                                                             force=False)
        mock_service.delete_snapshot.assert_called_once_with(snapshot)
        scenario._attach_volume.assert_called_once_with(
            scenario._boot_server.return_value, volume)
        scenario._detach_volume.assert_called_once_with(
            scenario._boot_server.return_value, volume)
        mock_service.delete_volume.assert_called_once_with(volume)

    @mock.patch("random.choice")
    def test_create_snapshot_and_attach_volume_use_volume_type_with_name(
            self, mock_choice):
        mock_service = self.mock_cinder.return_value

        scenario = volumes.CreateSnapshotAndAttachVolume(self._get_context())
        scenario._boot_server = mock.MagicMock()
        scenario._attach_volume = mock.MagicMock()
        scenario._detach_volume = mock.MagicMock()
        scenario.run("img", "flavor", volume_type="type")

        fake_volume = mock_service.create_volume.return_value
        fake_server = scenario._boot_server.return_value
        fake_snapshot = mock_service.create_snapshot.return_value

        mock_service.create_volume.assert_called_once_with(
            {"min": 1, "max": 5}, volume_type="type")
        mock_service.create_snapshot.assert_called_once_with(fake_volume.id,
                                                             force=False)
        mock_service.delete_snapshot.assert_called_once_with(fake_snapshot)
        scenario._attach_volume.assert_called_once_with(fake_server,
                                                        fake_volume)
        scenario._detach_volume.assert_called_once_with(fake_server,
                                                        fake_volume)
        mock_service.delete_volume.assert_called_once_with(fake_volume)

    @mock.patch("random.randint")
    def test_create_nested_snapshots_and_attach_volume(self, mock_randint):
        mock_service = self.mock_cinder.return_value
        mock_randint.return_value = 2
        volume_kwargs = {"volume_type": "type1"}
        snapshot_kwargs = {"name": "snapshot1", "description": "snaphot one"}

        scenario = volumes.CreateNestedSnapshotsAndAttachVolume(
            context=self._get_context())
        scenario._boot_server = mock.MagicMock()
        scenario._attach_volume = mock.MagicMock()
        scenario._detach_volume = mock.MagicMock()
        scenario.run("img", "flavor", create_volume_kwargs=volume_kwargs,
                     create_snapshot_kwargs=snapshot_kwargs)

        mock_service.create_volume.assert_called_once_with(
            mock_randint.return_value, **volume_kwargs)
        mock_service.create_snapshot.assert_called_once_with(
            mock_service.create_volume.return_value.id, force=False,
            **snapshot_kwargs)
        scenario._attach_volume(scenario._boot_server.return_value,
                                mock_service.create_volume.return_value)
        mock_service.delete_volume.assert_called_once_with(
            mock_service.create_volume.return_value)
        mock_service.delete_snapshot.assert_called_once_with(
            mock_service.create_snapshot.return_value)
        scenario._detach_volume.assert_called_once_with(
            scenario._boot_server.return_value,
            mock_service.create_volume.return_value)

    @mock.patch("random.randint")
    def test_create_nested_snapshots_and_attach_volume_2(self, mock_randint):
        mock_service = self.mock_cinder.return_value
        mock_randint.return_value = 2
        nested_level = 3
        volume_size = mock_randint.return_value
        fake_volumes = [mock.Mock(size=volume_size)
                        for i in range(nested_level)]
        fake_snapshots = [mock.Mock()
                          for i in range(nested_level)]
        mock_service.create_volume.side_effect = fake_volumes
        mock_service.create_snapshot.side_effect = fake_snapshots

        scenario = volumes.CreateNestedSnapshotsAndAttachVolume(
            context=self._get_context())
        scenario._boot_server = mock.MagicMock()
        scenario._attach_volume = mock.MagicMock()
        scenario._detach_volume = mock.MagicMock()
        scenario.run("img", "flavor", nested_level=nested_level)

        expected_volumes = [mock.call(volume_size)]
        expected_snapshots = [mock.call(fake_volumes[0].id, force=False)]
        expected_attachs = [mock.call(scenario._boot_server.return_value,
                                      fake_volumes[0])]
        for i in range(nested_level - 1):
            expected_volumes.append(
                mock.call(volume_size, snapshot_id=fake_snapshots[i].id))
            expected_snapshots.append(
                mock.call(fake_volumes[i + 1].id, force=False))
            expected_attachs.append(
                mock.call(scenario._boot_server.return_value,
                          fake_volumes[i + 1]))

        mock_service.create_volume.assert_has_calls(expected_volumes)
        mock_service.create_snapshot.assert_has_calls(expected_snapshots)
        scenario._attach_volume.assert_has_calls(expected_attachs)
        fake_volumes.reverse()
        fake_snapshots.reverse()
        mock_service.delete_volume.assert_has_calls(
            [mock.call(volume) for volume in fake_volumes])
        mock_service.delete_snapshot.assert_has_calls(
            [mock.call(snapshot) for snapshot in fake_snapshots])
        scenario._detach_volume.assert_has_calls(
            [mock.call(scenario._boot_server.return_value,
                       fake_volumes[i])
             for i in range(len(fake_volumes))])

    def test_create_volume_backup(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateVolumeBackup(self._get_context())

        volume_kwargs = {"some_var": "zaq"}
        scenario.run(1, do_delete=True, create_volume_kwargs=volume_kwargs)
        mock_service.create_volume.assert_called_once_with(1, **volume_kwargs)
        mock_service.create_backup.assert_called_once_with(
            mock_service.create_volume.return_value.id)
        mock_service.delete_volume.assert_called_once_with(
            mock_service.create_volume.return_value)
        mock_service.delete_backup.assert_called_once_with(
            mock_service.create_backup.return_value)

    def test_create_volume_backup_no_delete(self):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateVolumeBackup(self._get_context())

        volume_kwargs = {"some_var": "zaq"}
        scenario.run(1, do_delete=False, create_volume_kwargs=volume_kwargs)
        mock_service.create_volume.assert_called_once_with(1, **volume_kwargs)
        mock_service.create_backup.assert_called_once_with(
            mock_service.create_volume.return_value.id)
        self.assertFalse(mock_service.delete_volume.called)
        self.assertFalse(mock_service.delete_backup.called)

    def test_create_and_restore_volume_backup(self):
        mock_service = self.mock_cinder.return_value
        volume_kwargs = {"some_var": "zaq"}

        scenario = volumes.CreateAndRestoreVolumeBackup(self._get_context())
        scenario.run(1, do_delete=True, create_volume_kwargs=volume_kwargs)

        fake_volume = mock_service.create_volume.return_value
        fake_backup = mock_service.create_backup.return_value
        mock_service.create_volume.assert_called_once_with(1, **volume_kwargs)
        mock_service.create_backup.assert_called_once_with(fake_volume.id)
        mock_service.restore_backup.assert_called_once_with(fake_backup.id)
        mock_service.delete_volume.assert_called_once_with(fake_volume)
        mock_service.delete_backup.assert_called_once_with(fake_backup)

    def test_create_and_restore_volume_backup_no_delete(self):
        mock_service = self.mock_cinder.return_value
        volume_kwargs = {"some_var": "zaq"}
        scenario = volumes.CreateAndRestoreVolumeBackup(self._get_context())
        scenario.run(1, do_delete=False, create_volume_kwargs=volume_kwargs)

        fake_volume = mock_service.create_volume.return_value
        fake_backup = mock_service.create_backup.return_value
        mock_service.create_volume.assert_called_once_with(1, **volume_kwargs)
        mock_service.create_backup.assert_called_once_with(fake_volume.id)
        mock_service.restore_backup.assert_called_once_with(fake_backup.id)
        self.assertFalse(mock_service.delete_volume.called)
        self.assertFalse(mock_service.delete_backup.called)

    def test_create_and_list_volume_backups(self):
        mock_service = self.mock_cinder.return_value
        volume_kwargs = {"some_var": "zaq"}
        scenario = volumes.CreateAndListVolumeBackups(self._get_context())
        scenario.run(1, detailed=True, do_delete=True,
                     create_volume_kwargs=volume_kwargs)

        fake_volume = mock_service.create_volume.return_value
        fake_backup = mock_service.create_backup.return_value
        mock_service.create_volume.assert_called_once_with(1, **volume_kwargs)
        mock_service.create_backup.assert_called_once_with(fake_volume.id)
        mock_service.list_backups.assert_called_once_with(True)
        mock_service.delete_volume.assert_called_once_with(fake_volume)
        mock_service.delete_backup.assert_called_once_with(fake_backup)

    def test_create_and_list_volume_backups_no_delete(self):
        mock_service = self.mock_cinder.return_value
        volume_kwargs = {"some_var": "zaq"}
        scenario = volumes.CreateAndListVolumeBackups(self._get_context())
        scenario.run(1, detailed=True, do_delete=False,
                     create_volume_kwargs=volume_kwargs)

        fake_volume = mock_service.create_volume.return_value
        mock_service.create_volume.assert_called_once_with(1, **volume_kwargs)
        mock_service.create_backup.assert_called_once_with(fake_volume.id)
        mock_service.list_backups.assert_called_once_with(True)
        self.assertFalse(mock_service.delete_volume.called)
        self.assertFalse(mock_service.delete_backup.called)

    @ddt.data({},
              {"nested_level": 2},
              {"image": "img"})
    @ddt.unpack
    def test_create_volume_and_clone(self, nested_level=1,
                                     image=None):
        create_volumes_count = nested_level + 1
        fake_volumes = [mock.Mock(size=1)
                        for i in range(create_volumes_count)]
        mock_service = self.mock_cinder.return_value
        mock_service.create_volume.side_effect = fake_volumes

        scenario = volumes.CreateVolumeAndClone(self._get_context())
        scenario.run(1, image=image, nested_level=nested_level,
                     fakearg="fake")

        expected = [mock.call(1, imageRef=image, fakearg="fake")
                    if image else mock.call(1, fakearg="fake")]
        for i in range(nested_level):
            expected.append(mock.call(fake_volumes[i].size,
                                      source_volid=fake_volumes[i].id,
                                      fakearg="fake")
                            )
            self._test_atomic_action_timer(scenario.atomic_actions(),
                                           "cinder.clone_volume",
                                           count=nested_level)
        mock_service.create_volume.assert_has_calls(expected)

    def test_create_volume_from_snapshot(self):
        mock_service = self.mock_cinder.return_value
        create_snapshot_args = {"force": False}

        scenario = volumes.CreateVolumeFromSnapshot(self._get_context())
        scenario.run(fakearg="f")

        fake_snapshot = mock_service.create_snapshot.return_value
        fake_volume = mock_service.create_volume.return_value
        mock_service.create_snapshot.assert_called_once_with("uuid")
        mock_service.create_volume.assert_called_once_with(
            1, snapshot_id=fake_snapshot.id, fakearg="f")
        mock_service.delete_snapshot.assert_called_once_with(fake_snapshot)
        mock_service.delete_volume.assert_called_once_with(fake_volume)

        mock_service.create_snapshot.reset_mock()
        mock_service.create_volume.reset_mock()
        mock_service.delete_snapshot.reset_mock()
        mock_service.delete_volume.reset_mock()

        scenario.run(do_delete=False,
                     create_snapshot_kwargs=create_snapshot_args,
                     fakearg="f")

        mock_service.create_snapshot.assert_called_once_with(
            "uuid", **create_snapshot_args)
        mock_service.create_volume.assert_called_once_with(
            1, snapshot_id=fake_snapshot.id, fakearg="f")
        self.assertFalse(mock_service.delete_snapshot.called)
        self.assertFalse(mock_service.delete_volume.called)

    @ddt.data({},
              {"image": "img"})
    @ddt.unpack
    def test_create_and_accept_transfer(self, image=None):
        mock_service = self.mock_cinder.return_value
        scenario = volumes.CreateAndAcceptTransfer(self._get_context())
        scenario.run(1, image=image, fakearg="fake")

        expected = [mock.call(1, imageRef=image, fakearg="fake")
                    if image else mock.call(1, fakearg="fake")]
        mock_service.create_volume.assert_has_calls(expected)
        mock_service.transfer_create.assert_called_once_with(
            mock_service.create_volume.return_value.id)
        mock_service.transfer_accept.assert_called_once_with(
            mock_service.transfer_create.return_value.id,
            auth_key=mock_service.transfer_create.return_value.auth_key)
