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

from openstack.block_storage.v3 import volume
import openstack.cloud
from openstack.cloud import meta
from openstack.compute.v2 import volume_attachment
from openstack.tests import fakes
from openstack.tests.unit import base


class TestVolume(base.TestCase):

    def _compare_volumes(self, exp, real):
        self.assertDictEqual(
            volume.Volume(**exp).to_dict(computed=False),
            real.to_dict(computed=False)
        )

    def _compare_volume_attachments(self, exp, real):
        self.assertDictEqual(
            volume_attachment.VolumeAttachment(**exp).to_dict(computed=False),
            real.to_dict(computed=False)
        )

    def test_attach_volume(self):
        server = dict(id='server001')
        vol = {'id': 'volume001', 'status': 'available',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        rattach = {'server_id': server['id'], 'device': 'device001',
                   'volumeId': volume['id'], 'id': 'attachmentId'}
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments']),
                 json={'volumeAttachment': rattach},
                 validate=dict(json={
                     'volumeAttachment': {
                         'volumeId': vol['id']}})
                 )])
        ret = self.cloud.attach_volume(server, volume, wait=False)
        self._compare_volume_attachments(rattach, ret)
        self.assert_calls()

    def test_attach_volume_exception(self):
        server = dict(id='server001')
        vol = {'id': 'volume001', 'status': 'available',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments']),
                 status_code=404,
                 validate=dict(json={
                     'volumeAttachment': {
                         'volumeId': vol['id']}})
                 )])
        with testtools.ExpectedException(
            openstack.cloud.OpenStackCloudURINotFound
        ):
            self.cloud.attach_volume(server, volume, wait=False)
        self.assert_calls()

    def test_attach_volume_wait(self):
        server = dict(id='server001')
        vol = {'id': 'volume001', 'status': 'available',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        vol['attachments'] = [{'server_id': server['id'],
                               'device': 'device001'}]
        vol['status'] = 'in-use'
        attached_volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        rattach = {'server_id': server['id'], 'device': 'device001',
                   'volumeId': volume['id'], 'id': 'attachmentId'}
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments']),
                 json={'volumeAttachment': rattach},
                 validate=dict(json={
                     'volumeAttachment': {
                         'volumeId': vol['id']}})),
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', vol['id']]),
                 json={'volume': volume}),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', vol['id']]),
                 json={'volume': attached_volume})
        ])
        # defaults to wait=True
        ret = self.cloud.attach_volume(server, volume)
        self._compare_volume_attachments(rattach, ret)
        self.assert_calls()

    def test_attach_volume_wait_error(self):
        server = dict(id='server001')
        vol = {'id': 'volume001', 'status': 'available',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        vol['status'] = 'error'
        errored_volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        rattach = {'server_id': server['id'], 'device': 'device001',
                   'volumeId': volume['id'], 'id': 'attachmentId'}
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments']),
                 json={'volumeAttachment': rattach},
                 validate=dict(json={
                     'volumeAttachment': {
                         'volumeId': vol['id']}})),
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume['id']]),
                 json={'volume': errored_volume}),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume['id']]),
                 json={'volume': errored_volume})
        ])

        with testtools.ExpectedException(
            openstack.exceptions.ResourceFailure
        ):
            self.cloud.attach_volume(server, volume)
        self.assert_calls()

    def test_attach_volume_not_available(self):
        server = dict(id='server001')
        volume = dict(id='volume001', status='error', attachments=[])

        with testtools.ExpectedException(
            openstack.cloud.OpenStackCloudException,
            "Volume %s is not available. Status is '%s'" % (
                volume['id'], volume['status'])
        ):
            self.cloud.attach_volume(server, volume)
        self.assertEqual(0, len(self.adapter.request_history))

    def test_attach_volume_already_attached(self):
        device_id = 'device001'
        server = dict(id='server001')
        volume = dict(id='volume001',
                      attachments=[
                          {'server_id': 'server001', 'device': device_id}
                      ])

        with testtools.ExpectedException(
            openstack.cloud.OpenStackCloudException,
            "Volume %s already attached to server %s on device %s" % (
                volume['id'], server['id'], device_id)
        ):
            self.cloud.attach_volume(server, volume)
        self.assertEqual(0, len(self.adapter.request_history))

    def test_detach_volume(self):
        server = dict(id='server001')
        volume = dict(id='volume001',
                      attachments=[
                          {'server_id': 'server001', 'device': 'device001'}
                      ])
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='DELETE',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments', volume['id']]))])
        self.cloud.detach_volume(server, volume, wait=False)
        self.assert_calls()

    def test_detach_volume_exception(self):
        server = dict(id='server001')
        volume = dict(id='volume001',
                      attachments=[
                          {'server_id': 'server001', 'device': 'device001'}
                      ])
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='DELETE',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments', volume['id']]),
                 status_code=404)])
        with testtools.ExpectedException(
            openstack.cloud.OpenStackCloudURINotFound
        ):
            self.cloud.detach_volume(server, volume, wait=False)
        self.assert_calls()

    def test_detach_volume_wait(self):
        server = dict(id='server001')
        attachments = [{'server_id': 'server001', 'device': 'device001'}]
        vol = {'id': 'volume001', 'status': 'attached', 'name': '',
               'attachments': attachments}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        vol['status'] = 'available'
        vol['attachments'] = []
        avail_volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='DELETE',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments', volume.id])),
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', 'detail']),
                 json={'volumes': [avail_volume]})])
        self.cloud.detach_volume(server, volume)
        self.assert_calls()

    def test_detach_volume_wait_error(self):
        server = dict(id='server001')
        attachments = [{'server_id': 'server001', 'device': 'device001'}]
        vol = {'id': 'volume001', 'status': 'attached', 'name': '',
               'attachments': attachments}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        vol['status'] = 'error'
        vol['attachments'] = []
        errored_volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_nova_discovery_mock_dict(),
            dict(method='DELETE',
                 uri=self.get_mock_url(
                     'compute', 'public',
                     append=['servers', server['id'],
                             'os-volume_attachments', volume.id])),
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', 'detail']),
                 json={'volumes': [errored_volume]}),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public',
                     append=['volumes', errored_volume['id']]),
                 json={'volume': errored_volume})
        ])
        with testtools.ExpectedException(
            openstack.exceptions.ResourceFailure
        ):
            self.cloud.detach_volume(server, volume)
        self.assert_calls()

    def test_delete_volume_deletes(self):
        vol = {'id': 'volume001', 'status': 'attached',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume.id]),
                 json={'volumes': [volume]}),
            dict(method='DELETE',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume.id])),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume.id]),
                 status_code=404)])
        self.assertTrue(self.cloud.delete_volume(volume['id']))
        self.assert_calls()

    def test_delete_volume_gone_away(self):
        vol = {'id': 'volume001', 'status': 'attached',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume.id]),
                 json=volume),
            dict(method='DELETE',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume.id]),
                 status_code=404),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume.id]),
                 status_code=404),
        ])
        self.assertTrue(self.cloud.delete_volume(volume['id']))
        self.assert_calls()

    def test_delete_volume_force(self):
        vol = {'id': 'volume001', 'status': 'attached',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume['id']]),
                 json={'volumes': [volume]}),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'volumev3', 'public',
                     append=['volumes', volume.id, 'action']),
                 validate=dict(
                     json={'os-force_delete': {}})),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', volume['id']]),
                 status_code=404)])
        self.assertTrue(self.cloud.delete_volume(volume['id'], force=True))
        self.assert_calls()

    def test_set_volume_bootable(self):
        vol = {'id': 'volume001', 'status': 'attached',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', 'detail']),
                 json={'volumes': [volume]}),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'volumev3', 'public',
                     append=['volumes', volume.id, 'action']),
                 json={'os-set_bootable': {'bootable': True}}),
        ])
        self.cloud.set_volume_bootable(volume['id'])
        self.assert_calls()

    def test_set_volume_bootable_false(self):
        vol = {'id': 'volume001', 'status': 'attached',
               'name': '', 'attachments': []}
        volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes', 'detail']),
                 json={'volumes': [volume]}),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'volumev3', 'public',
                     append=['volumes', volume.id, 'action']),
                 json={'os-set_bootable': {'bootable': False}}),
        ])
        self.cloud.set_volume_bootable(volume['id'])
        self.assert_calls()

    def test_get_volume_by_id(self):
        vol1 = meta.obj_to_munch(fakes.FakeVolume('01', 'available', 'vol1'))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='GET',
                 uri=self.get_mock_url(
                     'volumev3', 'public',
                     append=['volumes', '01']),
                 json={'volume': vol1}
                 )
        ])
        self._compare_volumes(vol1, self.cloud.get_volume_by_id('01'))
        self.assert_calls()

    def test_create_volume(self):
        vol1 = meta.obj_to_munch(fakes.FakeVolume('01', 'available', 'vol1'))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes']),
                 json={'volume': vol1},
                 validate=dict(json={
                     'volume': {
                         'size': 50,
                         'name': 'vol1',
                     }})),
        ])

        self.cloud.create_volume(50, name='vol1')
        self.assert_calls()

    def test_create_bootable_volume(self):
        vol1 = meta.obj_to_munch(fakes.FakeVolume('01', 'available', 'vol1'))
        self.register_uris([
            self.get_cinder_discovery_mock_dict(),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'volumev3', 'public', append=['volumes']),
                 json={'volume': vol1},
                 validate=dict(json={
                     'volume': {
                         'size': 50,
                         'name': 'vol1',
                     }})),
            dict(method='POST',
                 uri=self.get_mock_url(
                     'volumev3', 'public',
                     append=['volumes', '01', 'action']),
                 validate=dict(
                     json={'os-set_bootable': {'bootable': True}})),
        ])

        self.cloud.create_volume(50, name='vol1', bootable=True)
        self.assert_calls()
