# Copyright 2013 Canonical 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.
""" Tests for create_volume TaskFlow """

import sys

import ddt
import mock

from castellan.common import exception as castellan_exc
from castellan.tests.unit.key_manager import mock_key_manager
from oslo_utils import imageutils

from cinder import context
from cinder import exception
from cinder.message import message_field
from cinder import test
from cinder.tests.unit.consistencygroup import fake_consistencygroup
from cinder.tests.unit import fake_constants as fakes
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit.image import fake as fake_image
from cinder.tests.unit import utils
from cinder.tests.unit.volume.flows import fake_volume_api
from cinder.volume.flows.api import create_volume
from cinder.volume.flows.manager import create_volume as create_volume_manager


@ddt.ddt
class CreateVolumeFlowTestCase(test.TestCase):

    def time_inc(self):
        self.counter += 1
        return self.counter

    def setUp(self):
        super(CreateVolumeFlowTestCase, self).setUp()
        self.ctxt = context.get_admin_context()

        # Ensure that time.time() always returns more than the last time it was
        # called to avoid div by zero errors.
        self.counter = float(0)
        self.get_extra_specs = self.patch(
            'cinder.volume.volume_types.get_volume_type_extra_specs',
            return_value={})

    @mock.patch('cinder.objects.Volume.get_by_id')
    @mock.patch('cinder.volume.utils.extract_host')
    @mock.patch('time.time')
    @mock.patch('cinder.objects.ConsistencyGroup.get_by_id')
    def test_cast_create_volume(self, consistencygroup_get_by_id, mock_time,
                                mock_extract_host, volume_get_by_id):
        mock_time.side_effect = self.time_inc
        volume = fake_volume.fake_volume_obj(self.ctxt)
        volume_get_by_id.return_value = volume
        props = {}
        cg_obj = (fake_consistencygroup.
                  fake_consistencyobject_obj(self.ctxt, consistencygroup_id=1,
                                             host='host@backend#pool'))
        consistencygroup_get_by_id.return_value = cg_obj
        spec = {'volume_id': None,
                'volume': None,
                'source_volid': None,
                'snapshot_id': None,
                'image_id': None,
                'consistencygroup_id': None,
                'cgsnapshot_id': None,
                'group_id': None, }

        # Fake objects assert specs
        task = create_volume.VolumeCastTask(
            fake_volume_api.FakeSchedulerRpcAPI(spec, self),
            fake_volume_api.FakeVolumeAPI(spec, self),
            fake_volume_api.FakeDb())

        task._cast_create_volume(self.ctxt, spec, props)

        spec = {'volume_id': volume.id,
                'volume': volume,
                'source_volid': 2,
                'snapshot_id': 3,
                'image_id': 4,
                'consistencygroup_id': 5,
                'cgsnapshot_id': None,
                'group_id': None, }

        # Fake objects assert specs
        task = create_volume.VolumeCastTask(
            fake_volume_api.FakeSchedulerRpcAPI(spec, self),
            fake_volume_api.FakeVolumeAPI(spec, self),
            fake_volume_api.FakeDb())

        task._cast_create_volume(self.ctxt, spec, props)
        consistencygroup_get_by_id.assert_called_once_with(self.ctxt, 5)
        mock_extract_host.assert_called_once_with('host@backend#pool')

    @ddt.data(('enabled', {'replication_enabled': '<is> True'}),
              ('disabled', {'replication_enabled': '<is> False'}),
              ('disabled', {}))
    @ddt.unpack
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_encryption_key_id', mock.Mock())
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    def test_extract_volume_request_replication_status(self,
                                                       replication_status,
                                                       extra_specs,
                                                       fake_get_qos):
        self.get_extra_specs.return_value = extra_specs
        fake_image_service = fake_image.FakeImageService()
        fake_key_manager = mock_key_manager.MockKeyManager()

        task = create_volume.ExtractVolumeRequestTask(fake_image_service,
                                                      {'nova'})

        result = task.execute(self.ctxt,
                              size=1,
                              snapshot=None,
                              image_id=None,
                              source_volume=None,
                              availability_zone='nova',
                              volume_type={'id': fakes.VOLUME_TYPE_ID,
                                           'size': 1},
                              metadata=None,
                              key_manager=fake_key_manager,
                              consistencygroup=None,
                              cgsnapshot=None,
                              group=None)
        self.assertEqual(replication_status, result['replication_status'],
                         extra_specs)

    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_encryption_key_id')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    def test_extract_volume_request_from_image_encrypted(
            self,
            fake_get_qos,
            fake_get_encryption_key,
            fake_get_volume_type_id,
            fake_is_encrypted):

        fake_image_service = fake_image.FakeImageService()
        image_id = 1
        image_meta = {}
        image_meta['id'] = image_id
        image_meta['status'] = 'active'
        image_meta['size'] = 1
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()

        task = create_volume.ExtractVolumeRequestTask(
            fake_image_service,
            {'nova'})

        fake_is_encrypted.return_value = True
        fake_get_volume_type_id.return_value = fakes.VOLUME_TYPE_ID
        task.execute(self.ctxt,
                     size=1,
                     snapshot=None,
                     image_id=image_id,
                     source_volume=None,
                     availability_zone='nova',
                     volume_type=None,
                     metadata=None,
                     key_manager=fake_key_manager,
                     consistencygroup=None,
                     cgsnapshot=None,
                     group=None)
        fake_get_encryption_key.assert_called_once_with(
            fake_key_manager, self.ctxt, fakes.VOLUME_TYPE_ID,
            None, None, image_meta)

    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    def test_extract_volume_request_from_image(
            self,
            fake_get_type_id,
            fake_get_qos,
            fake_is_encrypted):

        fake_image_service = fake_image.FakeImageService()
        image_id = 2
        image_meta = {}
        image_meta['id'] = image_id
        image_meta['status'] = 'active'
        image_meta['size'] = 1
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()
        volume_type = 'type1'

        task = create_volume.ExtractVolumeRequestTask(
            fake_image_service,
            {'nova'})

        fake_is_encrypted.return_value = False
        fake_get_type_id.return_value = 1
        fake_get_qos.return_value = {'qos_specs': None}
        result = task.execute(self.ctxt,
                              size=1,
                              snapshot=None,
                              image_id=image_id,
                              source_volume=None,
                              availability_zone='nova',
                              volume_type=volume_type,
                              metadata=None,
                              key_manager=fake_key_manager,
                              consistencygroup=None,
                              cgsnapshot=None,
                              group=None)
        expected_result = {'size': 1,
                           'snapshot_id': None,
                           'source_volid': None,
                           'availability_zone': 'nova',
                           'volume_type': volume_type,
                           'volume_type_id': 1,
                           'encryption_key_id': None,
                           'qos_specs': None,
                           'consistencygroup_id': None,
                           'cgsnapshot_id': None,
                           'group_id': None,
                           'refresh_az': False,
                           'replication_status': 'disabled'}
        self.assertEqual(expected_result, result)

    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    def test_extract_availability_zone_without_fallback(
            self,
            fake_get_type_id,
            fake_get_qos,
            fake_is_encrypted):
        fake_image_service = fake_image.FakeImageService()
        image_id = 3
        image_meta = {}
        image_meta['id'] = image_id
        image_meta['status'] = 'active'
        image_meta['size'] = 1
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()
        volume_type = 'type1'

        task = create_volume.ExtractVolumeRequestTask(
            fake_image_service,
            {'nova'})

        fake_is_encrypted.return_value = False
        fake_get_type_id.return_value = 1
        fake_get_qos.return_value = {'qos_specs': None}
        self.assertRaises(exception.InvalidAvailabilityZone,
                          task.execute,
                          self.ctxt,
                          size=1,
                          snapshot=None,
                          image_id=image_id,
                          source_volume=None,
                          availability_zone='notnova',
                          volume_type=volume_type,
                          metadata=None,
                          key_manager=fake_key_manager,
                          consistencygroup=None,
                          cgsnapshot=None,
                          group=None)

    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    def test_extract_availability_zone_with_fallback(
            self,
            fake_get_type_id,
            fake_get_qos,
            fake_is_encrypted):

        self.override_config('allow_availability_zone_fallback', True)

        fake_image_service = fake_image.FakeImageService()
        image_id = 4
        image_meta = {}
        image_meta['id'] = image_id
        image_meta['status'] = 'active'
        image_meta['size'] = 1
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()
        volume_type = 'type1'

        task = create_volume.ExtractVolumeRequestTask(
            fake_image_service,
            {'nova'})

        fake_is_encrypted.return_value = False
        fake_get_type_id.return_value = 1
        fake_get_qos.return_value = {'qos_specs': None}
        result = task.execute(self.ctxt,
                              size=1,
                              snapshot=None,
                              image_id=image_id,
                              source_volume=None,
                              availability_zone='does_not_exist',
                              volume_type=volume_type,
                              metadata=None,
                              key_manager=fake_key_manager,
                              consistencygroup=None,
                              cgsnapshot=None,
                              group=None)
        expected_result = {'size': 1,
                           'snapshot_id': None,
                           'source_volid': None,
                           'availability_zone': 'nova',
                           'volume_type': volume_type,
                           'volume_type_id': 1,
                           'encryption_key_id': None,
                           'qos_specs': None,
                           'consistencygroup_id': None,
                           'cgsnapshot_id': None,
                           'group_id': None,
                           'refresh_az': True,
                           'replication_status': 'disabled'}
        self.assertEqual(expected_result, result)

    @mock.patch('cinder.volume.volume_types.is_encrypted',
                return_value=True)
    @mock.patch('cinder.volume.volume_types.get_volume_type_encryption',
                return_value=mock.Mock(cipher='my-cipher-2000'))
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs',
                return_value={'qos_specs': None})
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask._get_volume_type_id',
                return_value=1)
    def test_get_encryption_key_id_castellan_error(
            self,
            mock_get_type_id,
            mock_get_qos,
            mock_get_volume_type_encryption,
            mock_is_encrypted):

        fake_image_service = fake_image.FakeImageService()
        image_id = 99
        image_meta = {'id': image_id,
                      'status': 'active',
                      'size': 1}
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()
        volume_type = 'type1'

        with mock.patch.object(fake_key_manager, 'create_key',
                               side_effect=castellan_exc.KeyManagerError):
            with mock.patch.object(fake_key_manager, 'get',
                                   return_value=fakes.ENCRYPTION_KEY_ID):

                task = create_volume.ExtractVolumeRequestTask(
                    fake_image_service,
                    {'nova'})

                self.assertRaises(exception.Invalid,
                                  task.execute,
                                  self.ctxt,
                                  size=1,
                                  snapshot=None,
                                  image_id=image_id,
                                  source_volume=None,
                                  availability_zone='nova',
                                  volume_type=volume_type,
                                  metadata=None,
                                  key_manager=fake_key_manager,
                                  consistencygroup=None,
                                  cgsnapshot=None,
                                  group=None)

        mock_is_encrypted.assert_called_once_with(self.ctxt, 1)
        mock_get_volume_type_encryption.assert_called_once_with(self.ctxt, 1)

    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    def test_extract_volume_request_task_with_large_volume_size(
            self,
            fake_get_type_id,
            fake_get_qos,
            fake_is_encrypted):
        fake_image_service = fake_image.FakeImageService()
        image_id = 11
        image_meta = {}
        image_meta['id'] = image_id
        image_meta['status'] = 'active'
        image_meta['size'] = 1
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()
        volume_type = 'type1'

        task = create_volume.ExtractVolumeRequestTask(
            fake_image_service,
            {'nova'})

        fake_is_encrypted.return_value = False
        fake_get_type_id.return_value = 1
        fake_get_qos.return_value = {'qos_specs': None}
        result = task.execute(self.ctxt,
                              size=(sys.maxsize + 1),
                              snapshot=None,
                              image_id=image_id,
                              source_volume=None,
                              availability_zone=None,
                              volume_type=volume_type,
                              metadata=None,
                              key_manager=fake_key_manager,
                              consistencygroup=None,
                              cgsnapshot=None,
                              group=None)
        expected_result = {'size': (sys.maxsize + 1),
                           'snapshot_id': None,
                           'source_volid': None,
                           'availability_zone': 'nova',
                           'volume_type': volume_type,
                           'volume_type_id': 1,
                           'encryption_key_id': None,
                           'qos_specs': None,
                           'replication_status': 'disabled',
                           'consistencygroup_id': None,
                           'cgsnapshot_id': None,
                           'refresh_az': False,
                           'group_id': None, }
        self.assertEqual(expected_result, result)

    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    def test_extract_volume_request_from_image_with_qos_specs(
            self,
            fake_get_type_id,
            fake_get_qos,
            fake_is_encrypted):

        fake_image_service = fake_image.FakeImageService()
        image_id = 5
        image_meta = {}
        image_meta['id'] = image_id
        image_meta['status'] = 'active'
        image_meta['size'] = 1
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()
        volume_type = 'type1'

        task = create_volume.ExtractVolumeRequestTask(
            fake_image_service,
            {'nova'})

        fake_is_encrypted.return_value = False
        fake_get_type_id.return_value = 1
        fake_qos_spec = {'specs': {'fake_key': 'fake'}}
        fake_get_qos.return_value = {'qos_specs': fake_qos_spec}
        result = task.execute(self.ctxt,
                              size=1,
                              snapshot=None,
                              image_id=image_id,
                              source_volume=None,
                              availability_zone='nova',
                              volume_type=volume_type,
                              metadata=None,
                              key_manager=fake_key_manager,
                              consistencygroup=None,
                              cgsnapshot=None,
                              group=None)
        expected_result = {'size': 1,
                           'snapshot_id': None,
                           'source_volid': None,
                           'availability_zone': 'nova',
                           'volume_type': volume_type,
                           'volume_type_id': 1,
                           'encryption_key_id': None,
                           'qos_specs': {'fake_key': 'fake'},
                           'consistencygroup_id': None,
                           'cgsnapshot_id': None,
                           'group_id': None,
                           'refresh_az': False,
                           'replication_status': 'disabled'}
        self.assertEqual(expected_result, result)

    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    @mock.patch('cinder.volume.volume_types.get_default_volume_type')
    @mock.patch('cinder.volume.volume_types.get_volume_type_by_name')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    def test_extract_image_volume_type_from_image(
            self,
            fake_get_type_id,
            fake_get_vol_type,
            fake_get_def_vol_type,
            fake_get_qos,
            fake_is_encrypted):

        image_volume_type = 'type_from_image'
        fake_image_service = fake_image.FakeImageService()
        image_id = 6
        image_meta = {}
        image_meta['id'] = image_id
        image_meta['status'] = 'active'
        image_meta['size'] = 1
        image_meta['properties'] = {}
        image_meta['properties']['cinder_img_volume_type'] = image_volume_type
        fake_image_service.create(self.ctxt, image_meta)
        fake_key_manager = mock_key_manager.MockKeyManager()

        task = create_volume.ExtractVolumeRequestTask(
            fake_image_service,
            {'nova'})

        fake_is_encrypted.return_value = False
        fake_get_type_id.return_value = 1
        fake_get_vol_type.return_value = image_volume_type
        fake_get_def_vol_type.return_value = 'fake_vol_type'
        fake_get_qos.return_value = {'qos_specs': None}
        result = task.execute(self.ctxt,
                              size=1,
                              snapshot=None,
                              image_id=image_id,
                              source_volume=None,
                              availability_zone='nova',
                              volume_type=None,
                              metadata=None,
                              key_manager=fake_key_manager,
                              consistencygroup=None,
                              cgsnapshot=None,
                              group=None)
        expected_result = {'size': 1,
                           'snapshot_id': None,
                           'source_volid': None,
                           'availability_zone': 'nova',
                           'volume_type': image_volume_type,
                           'volume_type_id': 1,
                           'encryption_key_id': None,
                           'qos_specs': None,
                           'consistencygroup_id': None,
                           'cgsnapshot_id': None,
                           'group_id': None,
                           'refresh_az': False,
                           'replication_status': 'disabled'}
        self.assertEqual(expected_result, result)

    @mock.patch('cinder.db.volume_type_get_by_name')
    @mock.patch('cinder.volume.volume_types.is_encrypted')
    @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
    @mock.patch('cinder.volume.volume_types.get_default_volume_type')
    @mock.patch('cinder.volume.flows.api.create_volume.'
                'ExtractVolumeRequestTask.'
                '_get_volume_type_id')
    def test_extract_image_volume_type_from_image_invalid_type(
            self,
            fake_get_type_id,
            fake_get_def_vol_type,
            fake_get_qos,
            fake_is_encrypted,
            fake_db_get_vol_type):

        image_volume_type = 'invalid'
        fake_image_service = fake_image.FakeImageService()
        image_id = 7