# Copyright (c) 2016 Chuck Fouts. 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

from cinder import context
from cinder import db
from cinder import exception
from cinder import objects
from cinder import quota
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_volume
from cinder.tests.unit import utils as tests_utils
from cinder.tests.unit import volume as base
from cinder.volume.flows.manager import manage_existing
from cinder.volume import manager
from cinder.volume import volume_utils

FAKE_HOST_POOL = 'volPool'
FAKE_HOST = 'hostname@backend'

QUOTAS = quota.QUOTAS


class ManageVolumeTestCase(base.BaseVolumeTestCase):

    def setUp(self):
        super(ManageVolumeTestCase, self).setUp()
        self.context = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
                                              True)
        self.manager = manager.VolumeManager()
        self.manager.stats = {'allocated_capacity_gb': 0, 'pools': {}}

    @staticmethod
    def _stub_volume_object_get(cls, host=FAKE_HOST):
        volume = {
            'id': fake.VOLUME_ID,
            'size': 1,
            'name': fake.VOLUME_NAME,
            'host': host,
        }
        return fake_volume.fake_volume_obj(cls.context, **volume)

    def test_manage_existing(self):
        volume_object = self._stub_volume_object_get(self)
        mock_run_flow_engine = self.mock_object(
            self.manager, '_run_manage_existing_flow_engine',
            return_value=volume_object)
        mock_update_volume_stats = self.mock_object(
            self.manager, '_update_stats_for_managed')

        result = self.manager.manage_existing(self.context, volume_object)

        self.assertEqual(fake.VOLUME_ID, result)
        mock_run_flow_engine.assert_called_once_with(self.context,
                                                     volume_object,
                                                     None)
        mock_update_volume_stats.assert_called_once_with(volume_object)

    def test_manage_existing_with_volume_object(self):
        volume_object = self._stub_volume_object_get(self)
        mock_object_volume = self.mock_object(objects.Volume, 'get_by_id')
        mock_run_flow_engine = self.mock_object(
            self.manager, '_run_manage_existing_flow_engine',
            return_value=volume_object)
        mock_update_volume_stats = self.mock_object(
            self.manager, '_update_stats_for_managed')

        result = self.manager.manage_existing(
            self.context, volume_object)

        self.assertEqual(fake.VOLUME_ID, result)
        mock_object_volume.assert_not_called()
        mock_run_flow_engine.assert_called_once_with(self.context,
                                                     volume_object,
                                                     None)
        mock_update_volume_stats.assert_called_once_with(volume_object)

    def test_run_manage_existing_flow_engine(self):
        mock_volume = mock.Mock()
        volume_object = self._stub_volume_object_get(self)

        mock_flow_engine = mock.Mock()
        mock_flow_engine_run = self.mock_object(mock_flow_engine, 'run')
        mock_flow_engine_fetch = self.mock_object(
            mock_flow_engine.storage, 'fetch', return_value=volume_object)
        mock_get_flow = self.mock_object(
            manage_existing, 'get_flow', return_value=mock_flow_engine)

        result = self.manager._run_manage_existing_flow_engine(self.context,
                                                               mock_volume,
                                                               None)

        self.assertEqual(volume_object, result)

        mock_get_flow.assert_called_once_with(self.context,
                                              self.manager.db,
                                              self.manager.driver,
                                              self.manager.host,
                                              mock_volume,
                                              None)
        mock_flow_engine_run.assert_called_once_with()
        mock_flow_engine_fetch.assert_called_once_with('volume')

    def test_run_manage_existing_flow_engine_exception(self):
        mock_get_flow = self.mock_object(
            manage_existing, 'get_flow', side_effect=Exception)
        volume_object = self._stub_volume_object_get(self)
        self.assertRaises(exception.CinderException,
                          self.manager._run_manage_existing_flow_engine,
                          self.context,
                          volume_object,
                          None)

        mock_get_flow.assert_called_once_with(self.context,
                                              self.manager.db,
                                              self.manager.driver,
                                              self.manager.host,
                                              volume_object,
                                              None)

    def test_update_stats_for_managed(self):
        volume_object = self._stub_volume_object_get(self,
                                                     host=FAKE_HOST +
                                                     '#volPool')
        self.manager._update_stats_for_managed(volume_object)
        backend_stats = self.manager.stats['pools'][FAKE_HOST_POOL]
        self.assertEqual(
            1, backend_stats['allocated_capacity_gb'])

    def test_update_stats_for_managed_no_pool(self):
        safe_get_backend = 'safe_get_backend'
        volume_obj = self._stub_volume_object_get(self)
        mock_safe_get = self.mock_object(
            self.manager.driver.configuration, 'safe_get',
            return_value=safe_get_backend)

        self.manager._update_stats_for_managed(volume_obj)

        mock_safe_get.assert_called_once_with('volume_backend_name')
        backend_stats = self.manager.stats['pools'][safe_get_backend]
        self.assertEqual(1, backend_stats['allocated_capacity_gb'])

    def test_update_stats_for_managed_default_backend(self):
        volume_obj = self._stub_volume_object_get(self)
        mock_safe_get = self.mock_object(
            self.manager.driver.configuration, 'safe_get', return_value=None)

        self.manager._update_stats_for_managed(volume_obj)

        mock_safe_get.assert_called_once_with('volume_backend_name')
        pool_stats = self.manager.stats['pools']
        backend_stats = pool_stats[volume_utils.DEFAULT_POOL_NAME]
        self.assertEqual(1, backend_stats['allocated_capacity_gb'])

    def test_update_stats_key_error(self):
        self.manager.stats = {}

        self.assertRaises(
            KeyError, self.manager._update_stats_for_managed,
            self._stub_volume_object_get(self))

    @mock.patch('cinder.volume.drivers.lvm.LVMVolumeDriver.'
                'manage_existing')
    @mock.patch('cinder.volume.drivers.lvm.LVMVolumeDriver.'
                'manage_existing_get_size')
    @mock.patch('cinder.volume.volume_utils.notify_about_volume_usage')
    def test_manage_volume_with_notify(self, mock_notify, mock_size,
                                       mock_manage):
        elevated = context.get_admin_context()
        vol_type = db.volume_type_create(
            elevated, {'name': 'type1', 'extra_specs': {}})
        # create source volume
        volume_params = {'volume_type_id': vol_type.id, 'status': 'managing'}
        test_vol = tests_utils.create_volume(self.context, **volume_params)
        mock_size.return_value = 1
        mock_manage.return_value = None

        self.volume.manage_existing(self.context, test_vol, 'volume_ref')
        mock_notify.assert_called_with(self.context, test_vol,
                                       'manage_existing.end',
                                       host=test_vol.host)

    @mock.patch('cinder.volume.drivers.lvm.LVMVolumeDriver.'
                'manage_existing_get_size')
    @mock.patch('cinder.volume.flows.manager.manage_existing.'
                'ManageExistingTask.execute')
    def test_manage_volume_raise_driver_exception(self, mock_execute,
                                                  mock_driver_get_size):
        elevated = context.get_admin_context()
        project_id = self.context.project_id
        db.volume_type_create(elevated, {'name': 'type1', 'extra_specs': {}})
        vol_type = db.volume_type_get_by_name(elevated, 'type1')
        # create source volume
        self.volume_params['volume_type_id'] = vol_type['id']
        self.volume_params['status'] = 'managing'
        test_vol = tests_utils.create_volume(self.context,
                                             **self.volume_params)
        mock_execute.side_effect = exception.VolumeBackendAPIException(
            data="volume driver got exception")
        mock_driver_get_size.return_value = 1
        # Set quota usage
        reserve_opts = {'volumes': 1, 'gigabytes': 1}
        reservations = QUOTAS.reserve(self.context, project_id=project_id,
                                      **reserve_opts)
        QUOTAS.commit(self.context, reservations)
        usage = db.quota_usage_get(self.context, project_id, 'volumes')
        volumes_in_use = usage.in_use
        usage = db.quota_usage_get(self.context, project_id, 'gigabytes')
        gigabytes_in_use = usage.in_use

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.volume.manage_existing,
                          self.context, test_vol,
                          'volume_ref')
        # check volume status
        volume = objects.Volume.get_by_id(context.get_admin_context(),
                                          test_vol.id)
        self.assertEqual('error_managing', volume.status)
        # Delete this volume with 'error_managing_deleting' status in c-vol.
        test_vol.status = 'error_managing_deleting'
        test_vol.save()
        self.volume.delete_volume(self.context, test_vol)
        ctxt = context.get_admin_context(read_deleted='yes')
        volume = objects.Volume.get_by_id(ctxt, test_vol.id)
        self.assertEqual('deleted', volume.status)
        # Get in_use number after deleting error_managing volume
        usage = db.quota_usage_get(self.context, project_id, 'volumes')
        volumes_in_use_new = usage.in_use
        self.assertEqual(volumes_in_use, volumes_in_use_new)
        usage = db.quota_usage_get(self.context, project_id, 'gigabytes')
        gigabytes_in_use_new = usage.in_use
        self.assertEqual(gigabytes_in_use, gigabytes_in_use_new)

    @mock.patch('cinder.volume.drivers.lvm.LVMVolumeDriver.'
                'manage_existing_get_size')
    def test_manage_volume_raise_driver_size_exception(self,
                                                       mock_driver_get_size):
        elevated = context.get_admin_context()
        project_id = self.context.project_id
        db.volume_type_create(elevated, {'name': 'type1', 'extra_specs': {}})
        # create source volume
        test_vol = tests_utils.create_volume(self.context,
                                             **self.volume_params)
        mock_driver_get_size.side_effect = exception.VolumeBackendAPIException(
            data="volume driver got exception")

        # Set quota usage
        reserve_opts = {'volumes': 1, 'gigabytes': 1}
        reservations = QUOTAS.reserve(self.context, project_id=project_id,
                                      **reserve_opts)
        QUOTAS.commit(self.context, reservations)
        usage = db.quota_usage_get(self.context, project_id, 'volumes')
        volumes_in_use = usage.in_use
        usage = db.quota_usage_get(self.context, project_id, 'gigabytes')
        gigabytes_in_use = usage.in_use

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.volume.manage_existing,
                          self.context, test_vol,
                          'volume_ref')
        # check volume status
        volume = objects.Volume.get_by_id(context.get_admin_context(),
                                          test_vol.id)
        self.assertEqual('error_managing', volume.status)
        # Delete this volume with 'error_managing_deleting' status in c-vol.
        test_vol.status = 'error_managing_deleting'
        test_vol.save()
        self.volume.delete_volume(self.context, test_vol)
        ctxt = context.get_admin_context(read_deleted='yes')
        volume = objects.Volume.get_by_id(ctxt, test_vol.id)
        self.assertEqual('deleted', volume.status)
        # Get in_use number after raising exception
        usage = db.quota_usage_get(self.context, project_id, 'volumes')
        volumes_in_use_new = usage.in_use
        self.assertEqual(volumes_in_use, volumes_in_use_new)
        usage = db.quota_usage_get(self.context, project_id, 'gigabytes')
        gigabytes_in_use_new = usage.in_use
        self.assertEqual(gigabytes_in_use, gigabytes_in_use_new)
