# Copyright 2019 NetApp, Inc.
# 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 ddt
import mock
import webob

from manila.api.v2 import share_servers
from manila.common import constants
from manila.db import api as db_api
from manila import exception
from manila import policy
from manila.share import api as share_api
from manila import test
from manila.tests.api import fakes
from manila.tests import db_utils
from manila import utils


@ddt.ddt
class ShareServerControllerTest(test.TestCase):
    """Share server api test"""

    def setUp(self):
        super(ShareServerControllerTest, self).setUp()
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        self.controller = share_servers.ShareServerController()
        self.resource_name = self.controller.resource_name

    @ddt.data(constants.STATUS_ACTIVE, constants.STATUS_ERROR,
              constants.STATUS_DELETING, constants.STATUS_CREATING,
              constants.STATUS_MANAGING, constants.STATUS_UNMANAGING,
              constants.STATUS_UNMANAGE_ERROR, constants.STATUS_MANAGE_ERROR)
    def test_share_server_reset_status(self, status):
        req = fakes.HTTPRequest.blank('/v2/share-servers/fake-share-server/',
                                      use_admin_context=True,
                                      version="2.49")
        body = {'reset_status': {'status': status}}

        context = req.environ['manila.context']
        mock_update = self.mock_object(db_api, 'share_server_update')

        result = self.controller.share_server_reset_status(
            req, 'fake_server_id', body)

        self.assertEqual(202, result.status_int)
        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'reset_status')
        mock_update.assert_called_once_with(
            context, 'fake_server_id', {'status': status})

    def test_share_reset_server_status_invalid(self):
        req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True,
                                      version="2.49")
        body = {'reset_status': {'status': constants.STATUS_EXTENDING}}
        context = req.environ['manila.context']

        self.assertRaises(
            webob.exc.HTTPBadRequest,
            self.controller.share_server_reset_status,
            req, id='fake_server_id', body=body)
        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'reset_status')

    def test_share_server_reset_status_no_body(self):
        req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True,
                                      version="2.49")
        context = req.environ['manila.context']

        self.assertRaises(
            webob.exc.HTTPBadRequest,
            self.controller.share_server_reset_status,
            req, id='fake_server_id', body={})
        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'reset_status')

    def test_share_server_reset_status_no_status(self):
        req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True,
                                      version="2.49")
        context = req.environ['manila.context']

        self.assertRaises(
            webob.exc.HTTPBadRequest,
            self.controller.share_server_reset_status,
            req, id='fake_server_id', body={'reset_status': {}})
        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'reset_status')

    def _setup_manage_test_request_body(self):
        body = {
            'share_network_id': 'fake_net_id',
            'share_network_subnet_id': 'fake_subnet_id',
            'host': 'fake_host',
            'identifier': 'fake_identifier',
            'driver_options': {'opt1': 'fake_opt1', 'opt2': 'fake_opt2'},
        }
        return body

    @ddt.data('fake_net_name', '')
    def test_manage(self, share_net_name):
        """Tests share server manage"""
        req = fakes.HTTPRequest.blank('/v2/share-servers/',
                                      use_admin_context=True,
                                      version="2.49")
        context = req.environ['manila.context']
        share_network = db_utils.create_share_network(name=share_net_name)
        share_net_subnet = db_utils.create_share_network_subnet(
            share_network_id=share_network['id'])
        share_server = db_utils.create_share_server(
            share_network_subnet_id=share_net_subnet['id'],
            host='fake_host',
            identifier='fake_identifier',
            is_auto_deletable=False)

        self.mock_object(db_api, 'share_network_get', mock.Mock(
            return_value=share_network))
        self.mock_object(db_api, 'share_network_subnet_get_default_subnet',
                         mock.Mock(return_value=share_net_subnet))
        self.mock_object(utils, 'validate_service_host')

        body = {
            'share_server': self._setup_manage_test_request_body()
        }

        manage_share_server_mock = self.mock_object(
            share_api.API, 'manage_share_server',
            mock.Mock(return_value=share_server))

        result = self.controller.manage(req, body)
        expected_result = {
            'share_server': {
                'id': share_server['id'],
                'project_id': 'fake',
                'updated_at': None,
                'status': constants.STATUS_ACTIVE,
                'host': 'fake_host',
                'share_network_id':
                    share_server['share_network_subnet']['share_network_id'],
                'created_at': share_server['created_at'],
                'backend_details': {},
                'identifier': share_server['identifier'],
                'is_auto_deletable': share_server['is_auto_deletable'],
            }
        }
        if share_net_name != '':
            expected_result['share_server']['share_network_name'] = (
                'fake_net_name')
        else:
            expected_result['share_server']['share_network_name'] = (
                share_net_subnet['share_network_id'])

        req_params = body['share_server']
        manage_share_server_mock.assert_called_once_with(
            context, req_params['identifier'], req_params['host'],
            share_net_subnet, req_params['driver_options'])

        self.assertEqual(expected_result, result)

        self.mock_policy_check.assert_called_once_with(
            context, self.resource_name, 'manage_share_server')

    def test_manage_invalid(self):
        req = fakes.HTTPRequest.blank('/manage_share_server',
                                      use_admin_context=True, version="2.49")
        context = req.environ['manila.context']
        share_network = db_utils.create_share_network()
        share_net_subnet = db_utils.create_share_network_subnet(
            share_network_id=share_network['id'])

        body = {
            'share_server': self._setup_manage_test_request_body()
        }
        self.mock_object(utils, 'validate_service_host')
        self.mock_object(db_api, 'share_network_get',
                         mock.Mock(return_value=share_network))
        self.mock_object(db_api, 'share_network_subnet_get_default_subnet',
                         mock.Mock(return_value=share_net_subnet))

        manage_share_server_mock = self.mock_object(
            share_api.API, 'manage_share_server',
            mock.Mock(side_effect=exception.InvalidInput('foobar')))

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.manage, req, body)

        req_params = body['share_server']
        manage_share_server_mock.assert_called_once_with(
            context, req_params['identifier'], req_params['host'],
            share_net_subnet, req_params['driver_options'])

    def test_manage_forbidden(self):
        """Tests share server manage without admin privileges"""
        req = fakes.HTTPRequest.blank('/manage_share_server', version="2.49")
        error = mock.Mock(side_effect=exception.PolicyNotAuthorized(action=''))
        self.mock_object(share_api.API, 'manage_share_server', error)

        share_network = db_utils.create_share_network()
        share_net_subnet = db_utils.create_share_network_subnet(
            share_network_id=share_network['id'])

        self.mock_object(db_api, 'share_network_get', mock.Mock(
            return_value=share_network))
        self.mock_object(db_api, 'share_network_subnet_get_default_subnet',
                         mock.Mock(return_value=share_net_subnet))
        self.mock_object(utils, 'validate_service_host')

        body = {
            'share_server': self._setup_manage_test_request_body()
        }

        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.manage,
                          req,
                          body)

    def test__validate_manage_share_server_validate_no_body(self):
        """Tests share server manage"""
        req = fakes.HTTPRequest.blank('/manage', version="2.49")
        body = {}

        self.assertRaises(webob.exc.HTTPUnprocessableEntity,
                          self.controller.manage,
                          req,
                          body)

    @ddt.data({'empty': False, 'key': 'host'},
              {'empty': False, 'key': 'share_network_id'},
              {'empty': False, 'key': 'identifier'},
              {'empty': True, 'key': 'host'},
              {'empty': True, 'key': 'share_network_id'},
              {'empty': True, 'key': 'identifier'})
    @ddt.unpack
    def test__validate_manage_share_server_validate_without_parameters(
            self, empty, key):
        """Tests share server manage without some parameters"""
        req = fakes.HTTPRequest.blank('/manage_share_server', version="2.49")
        self.mock_object(share_api.API, 'manage_share_server', mock.Mock())

        body = {
            'share_server': self._setup_manage_test_request_body(),
        }

        if empty:
            body['share_server'][key] = None
        else:
            body['share_server'].pop(key)

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.manage,
                          req,
                          body)

    @ddt.data(
        (webob.exc.HTTPBadRequest, exception.ServiceNotFound('foobar')),
        (webob.exc.HTTPBadRequest, exception.ServiceIsDown('foobar')),
        (webob.exc.HTTPForbidden, exception.PolicyNotAuthorized('foobar')),
        (webob.exc.HTTPForbidden, exception.AdminRequired())
    )
    @ddt.unpack
    def test__validate_manage_share_server_validate_service_host(
            self, exception_to_raise, side_effect_exception):
        req = fakes.HTTPRequest.blank('/manage', version="2.49")
        context = req.environ['manila.context']
        error = mock.Mock(side_effect=side_effect_exception)
        self.mock_object(utils, 'validate_service_host', error)

        share_network = db_utils.create_share_network()
        share_net_subnet = db_utils.create_share_network_subnet(
            share_network_id=share_network['id'])

        self.mock_object(db_api, 'share_network_get', mock.Mock(
            return_value=share_network))
        self.mock_object(db_api, 'share_network_subnet_get_default_subnet',
                         mock.Mock(return_value=share_net_subnet))

        self.assertRaises(
            exception_to_raise, self.controller.manage, req,
            {'share_server': self._setup_manage_test_request_body()})

        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'manage_share_server')

    def test__validate_manage_share_server_share_network_not_found(self):
        req = fakes.HTTPRequest.blank('/manage', version="2.49")
        context = req.environ['manila.context']
        self.mock_object(utils, 'validate_service_host')
        error = mock.Mock(
            side_effect=exception.ShareNetworkNotFound(share_network_id="foo"))
        self.mock_object(db_api, 'share_network_get', error)
        body = self._setup_manage_test_request_body()

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.manage,
                          req,
                          {'share_server': body})

        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'manage_share_server')

    def test__validate_manage_share_server_driver_opts_not_instance_dict(self):
        req = fakes.HTTPRequest.blank('/manage', version="2.49")
        context = req.environ['manila.context']
        self.mock_object(utils, 'validate_service_host')
        self.mock_object(db_api, 'share_network_get')
        body = self._setup_manage_test_request_body()
        body['driver_options'] = 'incorrect'
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.manage,
                          req,
                          {'share_server': body})

        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'manage_share_server')

    def test__validate_manage_share_server_error_extract_host(self):
        req = fakes.HTTPRequest.blank('/manage', version="2.49")
        context = req.environ['manila.context']
        body = self._setup_manage_test_request_body()
        body['host'] = 'fake@backend#pool'
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.manage,
                          req,
                          {'share_server': body})

        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'manage_share_server')

    @ddt.data(True, False)
    def test__validate_manage_share_server_error_subnet_not_found(
            self, body_contains_subnet):
        req = fakes.HTTPRequest.blank('/manage', version="2.51")
        context = req.environ['manila.context']
        share_network = db_utils.create_share_network()
        body = {'share_server': self._setup_manage_test_request_body()}
        share_net_subnet = db_utils.create_share_network_subnet(
            share_network_id=share_network['id'])
        body['share_server']['share_network_subnet_id'] = (
            share_net_subnet['id'] if body_contains_subnet else None)

        self.mock_object(
            db_api, 'share_network_subnet_get',
            mock.Mock(side_effect=exception.ShareNetworkSubnetNotFound(
                share_network_subnet_id='fake')))
        self.mock_object(db_api, 'share_network_subnet_get_default_subnet',
                         mock.Mock(return_value=None))

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.manage,
                          req,
                          body)

        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'manage_share_server')
        if body_contains_subnet:
            db_api.share_network_subnet_get.assert_called_once_with(
                context, share_net_subnet['id'])
        else:
            (db_api.share_network_subnet_get_default_subnet
                .assert_called_once_with(
                    context, body['share_server']['share_network_id']))

    @ddt.data(True, False)
    def test_unmanage(self, force):
        server = self._setup_unmanage_tests()
        req = fakes.HTTPRequest.blank('/unmanage', version="2.49")
        context = req.environ['manila.context']
        mock_get = self.mock_object(
            db_api, 'share_server_get', mock.Mock(return_value=server))
        mock_unmanage = self.mock_object(
            share_api.API, 'unmanage_share_server',
            mock.Mock(return_value=202))
        body = {'unmanage': {'force': force}}
        resp = self.controller.unmanage(req, server['id'], body)

        self.assertEqual(202, resp.status_int)

        mock_get.assert_called_once_with(context, server['id'])
        mock_unmanage.assert_called_once_with(context, server, force=force)

    def test_unmanage_share_server_not_found(self):
        """Tests unmanaging share servers"""
        req = fakes.HTTPRequest.blank('/v2/share-servers/fake_server_id/',
                                      version="2.49")
        context = req.environ['manila.context']
        share_server_error = mock.Mock(
            side_effect=exception.ShareServerNotFound(
                share_server_id='fake_server_id'))
        get_mock = self.mock_object(
            db_api, 'share_server_get', share_server_error)
        body = {'unmanage': {'force': True}}

        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.unmanage,
                          req,
                          'fake_server_id',
                          body)

        get_mock.assert_called_once_with(context, 'fake_server_id')

    @ddt.data(constants.STATUS_MANAGING, constants.STATUS_DELETING,
              constants.STATUS_CREATING, constants.STATUS_UNMANAGING)
    def test_unmanage_share_server_invalid_statuses(self, status):
        """Tests unmanaging share servers"""
        server = self._setup_unmanage_tests(status=status)
        get_mock = self.mock_object(db_api, 'share_server_get',
                                    mock.Mock(return_value=server))
        req = fakes.HTTPRequest.blank('/unmanage_share_server', version="2.49")
        context = req.environ['manila.context']
        body = {'unmanage': {'force': True}}

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.unmanage,
                          req,
                          server['id'],
                          body)

        get_mock.assert_called_once_with(context, server['id'])

    def _setup_unmanage_tests(self, status=constants.STATUS_ACTIVE):
        server = db_utils.create_share_server(
            id='fake_server_id', status=status)
        self.mock_object(db_api, 'share_server_get',
                         mock.Mock(return_value=server))
        return server

    @ddt.data(exception.ShareServerInUse, exception.PolicyNotAuthorized)
    def test_unmanage_share_server_badrequest(self, exc):
        req = fakes.HTTPRequest.blank('/unmanage', version="2.49")
        server = self._setup_unmanage_tests()
        context = req.environ['manila.context']
        error = mock.Mock(side_effect=exc('foobar'))
        mock_unmanage = self.mock_object(
            share_api.API, 'unmanage_share_server', error)
        body = {'unmanage': {'force': True}}

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.unmanage,
                          req,
                          'fake_server_id',
                          body)

        mock_unmanage.assert_called_once_with(context, server, force=True)
        policy.check_policy.assert_called_once_with(
            context, self.resource_name, 'unmanage_share_server')
