# Copyright (c) 2022 China Telecom Digital Intelligence.
# 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.

import http.client as http_client
from unittest import mock

import ddt
from oslo_serialization import jsonutils
import webob

from manila.api.v2 import share_transfer
from manila import context
from manila import db
from manila import exception
from manila import quota
from manila.share import api as share_api
from manila.share import rpcapi as share_rpcapi
from manila.share import share_types
from manila import test
from manila.tests.api import fakes
from manila.tests import db_utils
from manila.transfer import api as transfer_api

SHARE_TRANSFER_VERSION = "2.77"


@ddt.ddt
class ShareTransferAPITestCase(test.TestCase):
    """Test Case for transfers V3 API."""

    microversion = SHARE_TRANSFER_VERSION

    def setUp(self):
        super(ShareTransferAPITestCase, self).setUp()
        self.share_transfer_api = transfer_api.API()
        self.v2_controller = share_transfer.ShareTransferController()
        self.ctxt = context.RequestContext(
            'fake_user_id', 'fake_project_id', auth_token=True, is_admin=True)

    def _create_transfer(self, share_id='fake_share_id',
                         display_name='test_transfer'):
        transfer = self.share_transfer_api.create(context.get_admin_context(),
                                                  share_id, display_name)
        return transfer

    def _create_share(self, display_name='test_share',
                      display_description='this is a test share',
                      status='available',
                      size=1,
                      project_id='fake_project_id',
                      user_id='fake_user_id',
                      share_network_id=None,
                      mount_point_name=None):
        """Create a share object."""
        share_type = db_utils.create_share_type()
        instance_list = []
        if mount_point_name:
            instance_list.append(
                db_utils.create_share_instance(
                    status=status,
                    share_id='fake_id',
                    mount_point_name=mount_point_name
                )
            )

        share = db_utils.create_share(
            display_name=display_name,
            display_description=display_description,
            status=status,
            size=size,
            project_id=project_id,
            user_id=user_id,
            share_type_id=share_type['id'],
            share_network_id=share_network_id,
            instances=instance_list,
            mount_point_name=mount_point_name
        )

        share_id = share['id']

        return share_id

    def test_show_transfer(self):
        share_id = self._create_share(size=5)
        transfer = self._create_transfer(share_id)
        path = '/v2/fake_project_id/share-transfers/%s' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'GET'
        req.headers['Content-Type'] = 'application/json'
        res_dict = self.v2_controller.show(req, transfer['id'])

        self.assertEqual('test_transfer', res_dict['transfer']['name'])
        self.assertEqual(transfer['id'], res_dict['transfer']['id'])
        self.assertEqual(share_id, res_dict['transfer']['resource_id'])

    def test_list_transfers(self):
        share_id_1 = self._create_share(size=5)
        share_id_2 = self._create_share(size=5)
        transfer1 = self._create_transfer(share_id_1)
        transfer2 = self._create_transfer(share_id_2)

        path = '/v2/fake_project_id/share-transfers'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'GET'
        req.headers['Content-Type'] = 'application/json'
        res_dict = self.v2_controller.index(req)

        self.assertEqual(transfer1['id'], res_dict['transfers'][1]['id'])
        self.assertEqual('test_transfer', res_dict['transfers'][1]['name'])
        self.assertEqual(transfer2['id'], res_dict['transfers'][0]['id'])
        self.assertEqual('test_transfer', res_dict['transfers'][0]['name'])

    def test_list_transfers_with_all_tenants(self):
        share_id_1 = self._create_share(size=5)
        share_id_2 = self._create_share(size=5, project_id='fake_project_id2',
                                        user_id='fake_user_id2')
        self._create_transfer(share_id_1)
        self._create_transfer(share_id_2)

        path = '/v2/fake_project_id/share-transfers?all_tenants=true'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = context.get_admin_context()
        req.method = 'GET'
        req.headers['Content-Type'] = 'application/json'
        res_dict = self.v2_controller.index(req)

        self.assertEqual(2, len(res_dict['transfers']))

    def test_list_transfers_with_limit(self):
        share_id_1 = self._create_share(size=5)
        share_id_2 = self._create_share(size=5)
        self._create_transfer(share_id_1)
        self._create_transfer(share_id_2)
        path = '/v2/fake_project_id/share-transfers?limit=1'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'GET'
        req.headers['Content-Type'] = 'application/json'
        res_dict = self.v2_controller.index(req)

        self.assertEqual(1, len(res_dict['transfers']))

    @ddt.data("desc", "asc")
    def test_list_transfers_with_sort(self, sort_dir):
        share_id_1 = self._create_share(size=5)
        share_id_2 = self._create_share(size=5)
        transfer1 = self._create_transfer(share_id_1)
        transfer2 = self._create_transfer(share_id_2)
        path = \
            '/v2/fake_project_id/share-transfers?sort_key=id&sort_dir=%s' % (
                sort_dir)
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'GET'
        req.headers['Content-Type'] = 'application/json'
        res_dict = self.v2_controller.index(req)

        self.assertEqual(2, len(res_dict['transfers']))
        order_ids = sorted([transfer1['id'],
                            transfer2['id']])
        expect_result = order_ids[1] if sort_dir == "desc" else order_ids[0]
        self.assertEqual(expect_result,
                         res_dict['transfers'][0]['id'])

    def test_list_transfers_detail(self):
        share_id_1 = self._create_share(size=5)
        share_id_2 = self._create_share(size=5)
        transfer1 = self._create_transfer(share_id_1)
        transfer2 = self._create_transfer(share_id_2)

        path = '/v2/fake_project_id/share-transfers/detail'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'GET'
        req.headers['Content-Type'] = 'application/json'
        req.headers['Accept'] = 'application/json'
        res_dict = self.v2_controller.detail(req)

        self.assertEqual('test_transfer',
                         res_dict['transfers'][1]['name'])
        self.assertEqual(transfer1['id'], res_dict['transfers'][1]['id'])
        self.assertEqual(share_id_1, res_dict['transfers'][1]['resource_id'])

        self.assertEqual('test_transfer',
                         res_dict['transfers'][0]['name'])
        self.assertEqual(transfer2['id'], res_dict['transfers'][0]['id'])
        self.assertEqual(share_id_2, res_dict['transfers'][0]['resource_id'])

    def test_create_transfer(self):
        share_id = self._create_share(status='available', size=5)
        body = {"transfer": {"name": "transfer1",
                             "share_id": share_id}}

        path = '/v2/fake_project_id/share-transfers'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        res_dict = self.v2_controller.create(req, body)

        self.assertIn('id', res_dict['transfer'])
        self.assertIn('auth_key', res_dict['transfer'])
        self.assertIn('created_at', res_dict['transfer'])
        self.assertIn('name', res_dict['transfer'])
        self.assertIn('resource_id', res_dict['transfer'])

    @ddt.data({},
              {"transfer": {"name": "transfer1"}},
              {"transfer": {"name": "transfer1",
                            "share_id": "invalid_share_id"}})
    def test_create_transfer_with_invalid_body(self, body):
        path = '/v2/fake_project_id/share-transfers'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.create, req, body)

    def test_create_transfer_with_invalid_share_status(self):
        share_id = self._create_share()
        body = {"transfer": {"name": "transfer1",
                             "share_id": share_id}}
        db.share_update(context.get_admin_context(),
                        share_id, {'status': 'error'})

        path = '/v2/fake_project_id/share-transfers'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.create, req, body)

    def test_create_transfer_with_invalid_mount_point_name(self):
        share_id = self._create_share(
            project_id='fake_pid',
            mount_point_name='fake_pid_mount_point_name')
        body = {"transfer": {"name": "transfer1",
                             "share_id": share_id}}
        db.share_update(context.get_admin_context(),
                        share_id, {'status': 'error'})

        path = '/v2/fake_project_id/share-transfers'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.create, req, body)

    def test_create_transfer_with_project_id_prefix_mount_point_name(self):
        share_id = self._create_share(project_id='fake',
                                      mount_point_name='fake_mp')

        self.assertRaises(exception.Invalid,
                          self.share_transfer_api.create,
                          context.get_admin_context(), share_id,
                          'test_missing_share_type')

    def test_create_transfer_share_with_network_id(self):
        share_id = self._create_share(share_network_id='fake_id')
        body = {"transfer": {"name": "transfer1",
                             "share_id": share_id}}

        path = '/v2/fake_project_id/share-transfers'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.create, req, body)

    def test_create_transfer_share_with_invalid_snapshot(self):
        share_id = self._create_share(share_network_id='fake_id')
        db_utils.create_snapshot(share_id=share_id)
        body = {"transfer": {"name": "transfer1",
                             "share_id": share_id}}

        path = '/v2/fake_project_id/share-transfers'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.create, req, body)

    def test_delete_transfer_awaiting_transfer(self):
        share_id = self._create_share()
        transfer = self.share_transfer_api.create(context.get_admin_context(),
                                                  share_id, 'test_transfer')
        path = '/v2/fake_project_id/share-transfers/%s' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'DELETE'
        req.headers['Content-Type'] = 'application/json'
        self.v2_controller.delete(req, transfer['id'])

        # verify transfer has been deleted
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'GET'
        req.headers['Content-Type'] = 'application/json'
        res = req.get_response(fakes.app())

        self.assertEqual(http_client.NOT_FOUND, res.status_int)
        self.assertEqual(db.share_get(context.get_admin_context(),
                         share_id)['status'], 'available')

    def test_delete_transfer_not_awaiting_transfer(self):
        share_id = self._create_share()
        transfer = self.share_transfer_api.create(context.get_admin_context(),
                                                  share_id, 'test_transfer')
        db.share_update(context.get_admin_context(),
                        share_id, {'status': 'available'})

        path = '/v2/fake_project_id/share-transfers/%s' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'DELETE'
        req.headers['Content-Type'] = 'application/json'
        self.assertRaises(exception.InvalidShare,
                          self.v2_controller.delete, req,
                          transfer['id'])

    def test_transfer_accept_share_id_specified(self):
        share_id = self._create_share()
        transfer = self.share_transfer_api.create(context.get_admin_context(),
                                                  share_id, 'test_transfer')
        self.mock_object(quota.QUOTAS, 'reserve', mock.Mock())
        self.mock_object(quota.QUOTAS, 'commit', mock.Mock())
        self.mock_object(share_api.API,
                         'check_is_share_size_within_per_share_quota_limit',
                         mock.Mock())
        self.mock_object(share_rpcapi.ShareAPI,
                         'transfer_accept',
                         mock.Mock())
        fake_share_type = {'id': 'fake_id',
                           'name': 'fake_name',
                           'is_public': True}
        self.mock_object(share_types, 'get_share_type',
                         mock.Mock(return_value=fake_share_type))
        self.mock_object(db, 'share_snapshot_get_all_for_share',
                         mock.Mock(return_value={}))

        body = {"accept": {"auth_key": transfer['auth_key']}}
        path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.v2_controller.accept(req, transfer['id'], body)

    def test_transfer_accept_with_not_public_share_type(self):
        share_id = self._create_share()
        transfer = self.share_transfer_api.create(context.get_admin_context(),
                                                  share_id, 'test_transfer')
        fake_share_type = {'id': 'fake_id',
                           'name': 'fake_name',
                           'is_public': False,
                           'projects': ['project_id1', 'project_id2']}
        self.mock_object(share_types, 'get_share_type',
                         mock.Mock(return_value=fake_share_type))

        body = {"accept": {"auth_key": transfer['auth_key']}}
        path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.accept, req,
                          transfer['id'], body)

    @ddt.data({},
              {"accept": {}},
              {"accept": {"auth_key": "fake_auth_key",
                          "clear_access_rules": "invalid_bool"}})
    def test_transfer_accept_with_invalid_body(self, body):
        path = '/v2/fake_project_id/share-transfers/fake_transfer_id/accept'
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.accept, req,
                          'fake_transfer_id', body)

    def test_transfer_accept_with_invalid_auth_key(self):
        share_id = self._create_share(size=5)
        transfer = self._create_transfer(share_id)
        body = {"accept": {"auth_key": "invalid_auth_key"}}
        path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.accept, req,
                          transfer['id'], body)

    def test_transfer_accept_with_invalid_share_status(self):
        share_id = self._create_share(size=5)
        transfer = self._create_transfer(share_id)
        db.share_update(context.get_admin_context(),
                        share_id, {'status': 'error'})
        body = {"accept": {"auth_key": transfer['auth_key']}}
        path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.v2_controller.accept, req,
                          transfer['id'], body)

    @ddt.data({'overs': {'gigabytes': 'fake'}},
              {'overs': {'shares': 'fake'}},
              {'overs': {'snapshot_gigabytes': 'fake'}},
              {'overs': {'snapshots': 'fake'}})
    @ddt.unpack
    def test_accept_share_over_quota(self, overs):
        share_id = self._create_share()
        db_utils.create_snapshot(share_id=share_id, status='available')
        transfer = self.share_transfer_api.create(context.get_admin_context(),
                                                  share_id, 'test_transfer')

        usages = {'gigabytes': {'reserved': 5, 'in_use': 5},
                  'shares': {'reserved': 10, 'in_use': 10},
                  'snapshot_gigabytes': {'reserved': 5, 'in_use': 5},
                  'snapshots': {'reserved': 10, 'in_use': 10}}

        quotas = {'gigabytes': 5, 'shares': 10,
                  'snapshot_gigabytes': 5, 'snapshots': 10}
        exc = exception.OverQuota(overs=overs, usages=usages, quotas=quotas)
        self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(side_effect=exc))
        self.mock_object(quota.QUOTAS, 'commit', mock.Mock())
        self.mock_object(share_api.API,
                         'check_is_share_size_within_per_share_quota_limit',
                         mock.Mock())
        self.mock_object(share_rpcapi.ShareAPI,
                         'transfer_accept',
                         mock.Mock())
        fake_share_type = {'id': 'fake_id',
                           'name': 'fake_name',
                           'is_public': True}
        self.mock_object(share_types, 'get_share_type',
                         mock.Mock(return_value=fake_share_type))

        body = {"accept": {"auth_key": transfer['auth_key']}}
        path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
        req = fakes.HTTPRequest.blank(path, version=self.microversion)
        req.environ['manila.context'] = self.ctxt
        req.method = 'POST'
        req.headers['Content-Type'] = 'application/json'
        req.body = jsonutils.dumps(body).encode("utf-8")
        self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
                          self.v2_controller.accept, req,
                          transfer['id'], body)
