# Copyright 2015 EMC Corporation
# 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

import ast
import ddt
from oslo_serialization import jsonutils
import webob

from manila.api.openstack import api_version_request as api_version
from manila.api.v2 import share_snapshots
from manila.common import constants
from manila import context
from manila import db
from manila import exception
from manila import policy
from manila.share import api as share_api
from manila import test
from manila.tests.api.contrib import stubs
from manila.tests.api import fakes
from manila.tests import db_utils
from manila.tests import fake_share
from manila import utils

MIN_MANAGE_SNAPSHOT_API_VERSION = '2.12'


def get_fake_manage_body(share_id=None, provider_location=None,
                         driver_options=None, **kwargs):
    fake_snapshot = {
        'share_id': share_id,
        'provider_location': provider_location,
        'driver_options': driver_options,
        'user_id': 'fake_user_id',
        'project_id': 'fake_project_id',
    }
    fake_snapshot.update(kwargs)
    return {'snapshot': fake_snapshot}


@ddt.ddt
class ShareSnapshotAPITest(test.TestCase):
    """Share Snapshot API Test."""

    def setUp(self):
        super(ShareSnapshotAPITest, self).setUp()
        self.controller = share_snapshots.ShareSnapshotsController()

        self.mock_object(share_api.API, 'get', stubs.stub_share_get)
        self.mock_object(share_api.API, 'get_all_snapshots',
                         stubs.stub_snapshot_get_all_by_project)
        self.mock_object(share_api.API, 'get_snapshot',
                         stubs.stub_snapshot_get)
        self.mock_object(share_api.API, 'snapshot_update',
                         stubs.stub_snapshot_update)
        self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True)
        )
        self.snp_example = {
            'share_id': 100,
            'size': 12,
            'force': False,
            'display_name': 'updated_snapshot_name',
            'display_description': 'updated_snapshot_description',
        }
        self.maxDiff = None

    @ddt.data('2.16', '2.17')
    def test_snapshot_create(self, version):
        self.mock_object(share_api.API, 'create_snapshot',
                         stubs.stub_snapshot_create)

        body = {
            'snapshot': {
                'share_id': 'fakeshareid',
                'force': False,
                'name': 'displaysnapname',
                'description': 'displaysnapdesc',
            }
        }
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots', version=version)

        res_dict = self.controller.create(req, body)

        expected = fake_share.expected_snapshot(version=version, id=200)

        self.assertEqual(expected, res_dict)

    @ddt.data(
        {'name': 'name1', 'description': 'x' * 256},
        {'name': 'x' * 256, 'description': 'description1'},
    )
    @ddt.unpack
    def test_snapshot_create_invalid_input(self, name, description):
        self.mock_object(share_api.API, 'create_snapshot')
        self.mock_object(
            share_api.API,
            'get',
            mock.Mock(return_value={'snapshot_support': True,
                                    'is_soft_deleted': False}))
        body = {
            'snapshot': {
                'share_id': 200,
                'force': False,
                'name': name,
                'description': description,
            }
        }
        req = fakes.HTTPRequest.blank('/fake/snapshots')

        self.assertRaises(
            exception.InvalidInput,
            self.controller.create, req, body)

    def test_snapshot_create_in_recycle_bin(self):
        self.mock_object(share_api.API, 'create_snapshot')
        self.mock_object(
            share_api.API,
            'get',
            mock.Mock(return_value={'snapshot_support': True,
                                    'is_soft_deleted': True}))
        body = {
            'snapshot': {
                'share_id': 200,
                'force': False,
                'name': 'fake_share_name',
                'description': 'fake_share_description',
            }
        }
        req = fakes.HTTPRequest.blank('/fake/snapshots')

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

        self.assertFalse(share_api.API.create_snapshot.called)

    @ddt.data(0, False)
    def test_snapshot_create_no_support(self, snapshot_support):
        self.mock_object(share_api.API, 'create_snapshot')
        self.mock_object(
            share_api.API,
            'get',
            mock.Mock(return_value={'snapshot_support': snapshot_support}))
        body = {
            'snapshot': {
                'share_id': 100,
                'force': False,
                'name': 'fake_share_name',
                'description': 'fake_share_description',
            }
        }
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots')

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

        self.assertFalse(share_api.API.create_snapshot.called)

    def test_snapshot_create_no_body(self):
        body = {}
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots')
        self.assertRaises(webob.exc.HTTPUnprocessableEntity,
                          self.controller.create,
                          req,
                          body)

    def test_snapshot_delete(self):
        self.mock_object(share_api.API, 'delete_snapshot',
                         stubs.stub_snapshot_delete)
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots/200')
        resp = self.controller.delete(req, 200)
        self.assertEqual(202, resp.status_int)

    def test_snapshot_delete_nofound(self):
        self.mock_object(share_api.API, 'get_snapshot',
                         stubs.stub_snapshot_get_notfound)
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots/200')
        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.delete,
                          req,
                          200)

    @ddt.data('2.0', '2.16', '2.17', '2.73')
    def test_snapshot_show(self, version):
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots/200',
                                      version=version)
        expected = fake_share.expected_snapshot(version=version, id=200)

        res_dict = self.controller.show(req, 200)

        self.assertEqual(expected, res_dict)

    def test_snapshot_show_status_none(self):
        return_snapshot = {
            'share_id': 100,
            'name': 'fake_share_name',
            'description': 'fake_share_description',
            'status': None,
        }
        self.mock_object(share_api.API, 'get_snapshot',
                         mock.Mock(return_value=return_snapshot))
        req = fakes.HTTPRequest.blank('/fake/snapshots/200')
        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.show,
                          req, '200')

    def test_snapshot_show_nofound(self):
        self.mock_object(share_api.API, 'get_snapshot',
                         stubs.stub_snapshot_get_notfound)
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots/200')
        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.show,
                          req, '200')

    def test_snapshot_list_summary(self):
        self.mock_object(share_api.API, 'get_all_snapshots',
                         stubs.stub_snapshot_get_all_by_project)
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots')
        res_dict = self.controller.index(req)
        expected = {
            'snapshots': [
                {
                    'name': 'displaysnapname',
                    'id': 2,
                    'links': [
                        {
                            'href': 'http://localhost/share/v2/fake/'
                                    'snapshots/2',
                            'rel': 'self'
                        },
                        {
                            'href': 'http://localhost/share/fake/snapshots/2',
                            'rel': 'bookmark'
                        }
                    ],
                }
            ]
        }
        self.assertEqual(expected, res_dict)

    def _snapshot_list_summary_with_search_opts(self, version,
                                                use_admin_context):
        search_opts = fake_share.search_opts()
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.36')):
            search_opts.pop('name')
            search_opts['display_name~'] = 'fake_name'
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.79')):
            search_opts.update({'with_count': 'true'})

        # fake_key should be filtered for non-admin
        url = '/v2/fake/snapshots?fake_key=fake_value'
        for k, v in search_opts.items():
            url = url + '&' + k + '=' + v
        req = fakes.HTTPRequest.blank(
            url, use_admin_context=use_admin_context, version=version)

        method = 'get_all_snapshots'
        db_snapshots = [
            {'id': 'id1', 'display_name': 'n1', 'status': 'fake_status', },
            {'id': 'id2', 'display_name': 'n2', 'status': 'fake_status', },
            {'id': 'id3', 'display_name': 'n3', 'status': 'fake_status', },
        ]

        mock_action = {'return_value': [db_snapshots[1]]}
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.79')):
            method = 'get_all_snapshots_with_count'
            mock_action = {'side_effect': [(1, [db_snapshots[1]])]}

        mock_get_all_snapshots = (
            self.mock_object(share_api.API, method, mock.Mock(**mock_action)))

        result = self.controller.index(req)

        search_opts_expected = {
            'status': search_opts['status'],
            'share_id': search_opts['share_id'],
        }
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.36')):
            search_opts_expected['display_name~'] = 'fake_name'
        else:
            search_opts_expected['display_name'] = search_opts['name']
        if use_admin_context:
            search_opts_expected.update({'fake_key': 'fake_value'})

        mock_get_all_snapshots.assert_called_once_with(
            req.environ['manila.context'],
            limit=int(search_opts['limit']),
            offset=int(search_opts['offset']),
            sort_key=search_opts['sort_key'],
            sort_dir=search_opts['sort_dir'],
            search_opts=search_opts_expected,
        )
        self.assertEqual(1, len(result['snapshots']))
        self.assertEqual(db_snapshots[1]['id'], result['snapshots'][0]['id'])
        self.assertEqual(
            db_snapshots[1]['display_name'], result['snapshots'][0]['name'])
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.79')):
            self.assertEqual(1, result['count'])

    @ddt.data({'version': '2.35', 'use_admin_context': True},
              {'version': '2.36', 'use_admin_context': True},
              {'version': '2.79', 'use_admin_context': True},
              {'version': '2.35', 'use_admin_context': False},
              {'version': '2.36', 'use_admin_context': False},
              {'version': '2.79', 'use_admin_context': False})
    @ddt.unpack
    def test_snapshot_list_summary_with_search_opts(self, version,
                                                    use_admin_context):
        self._snapshot_list_summary_with_search_opts(
            version=version, use_admin_context=use_admin_context)

    def test_snapshot_list_metadata_filter(self, version='2.73',
                                           use_admin_context=True):
        search_opts = {
            'sort_key': 'fake_sort_key',
            'sort_dir': 'fake_sort_dir',
            'offset': '1',
            'limit': '1',
            'metadata': "{'foo': 'bar'}"
        }
        # fake_key should be filtered for non-admin
        url = '/v2/fake/snapshots?fake_key=fake_value'
        for k, v in search_opts.items():
            url = url + '&' + k + '=' + v
        req = fakes.HTTPRequest.blank(
            url, use_admin_context=use_admin_context, version=version)

        snapshots = [
            {'id': 'id1', 'metadata': {'foo': 'bar'}}
        ]
        self.mock_object(share_api.API, 'get_all_snapshots',
                         mock.Mock(return_value=snapshots))

        result = self.controller.index(req)

        search_opts_expected = {
            'metadata': ast.literal_eval(search_opts['metadata'])
        }
        if use_admin_context:
            search_opts_expected.update({'fake_key': 'fake_value'})
        share_api.API.get_all_snapshots.assert_called_once_with(
            req.environ['manila.context'],
            limit=int(search_opts['limit']),
            offset=int(search_opts['offset']),
            sort_key=search_opts['sort_key'],
            sort_dir=search_opts['sort_dir'],
            search_opts=search_opts_expected,
        )
        self.assertEqual(1, len(result['snapshots']))
        self.assertEqual(snapshots[0]['id'], result['snapshots'][0]['id'])

    def _snapshot_list_detail_with_search_opts(self, version,
                                               use_admin_context):
        search_opts = fake_share.search_opts()
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.79')):
            search_opts.update({'with_count': 'true'})

        # fake_key should be filtered for non-admin
        url = '/v2/fake/shares/detail?fake_key=fake_value'
        for k, v in search_opts.items():
            url = url + '&' + k + '=' + v
        req = fakes.HTTPRequest.blank(url, use_admin_context=use_admin_context)

        method = 'get_all_snapshots'
        db_snapshots = [
            {
                'id': 'id1',
                'display_name': 'n1',
                'status': 'fake_status',
                'aggregate_status': 'fake_status',
            },
            {
                'id': 'id2',
                'display_name': 'n2',
                'status': 'someotherstatus',
                'aggregate_status': 'fake_status',
                'share_id': 'fake_share_id',
            },
            {
                'id': 'id3',
                'display_name': 'n3',
                'status': 'fake_status',
                'aggregate_status': 'fake_status',
            },
        ]
        mock_action = {'return_value': [db_snapshots[1]]}
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.79')):
            method = 'get_all_snapshots_with_count'
            mock_action = {'side_effect': [(1, [db_snapshots[1]])]}

        mock_get_all_snapshots = (
            self.mock_object(share_api.API, method, mock.Mock(**mock_action)))

        result = self.controller.detail(req)

        search_opts_expected = {
            'display_name': search_opts['name'],
            'status': search_opts['status'],
            'share_id': search_opts['share_id'],
        }
        if use_admin_context:
            search_opts_expected.update({'fake_key': 'fake_value'})
        mock_get_all_snapshots.assert_called_once_with(
            req.environ['manila.context'],
            limit=int(search_opts['limit']),
            offset=int(search_opts['offset']),
            sort_key=search_opts['sort_key'],
            sort_dir=search_opts['sort_dir'],
            search_opts=search_opts_expected,
        )
        self.assertEqual(1, len(result['snapshots']))
        self.assertEqual(db_snapshots[1]['id'], result['snapshots'][0]['id'])
        self.assertEqual(
            db_snapshots[1]['display_name'], result['snapshots'][0]['name'])
        self.assertEqual(
            db_snapshots[1]['aggregate_status'],
            result['snapshots'][0]['status'])
        self.assertEqual(
            db_snapshots[1]['share_id'], result['snapshots'][0]['share_id'])
        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.79')):
            self.assertEqual(1, result['count'])

    @ddt.data({'version': '2.78', 'use_admin_context': True},
              {'version': '2.78', 'use_admin_context': False},
              {'version': '2.79', 'use_admin_context': True},
              {'version': '2.79', 'use_admin_context': False})
    @ddt.unpack
    def test_snapshot_list_detail_with_search_opts(self, version,
                                                   use_admin_context):
        self._snapshot_list_detail_with_search_opts(
            version=version, use_admin_context=use_admin_context)

    @ddt.data('2.0', '2.16', '2.17')
    def test_snapshot_list_detail(self, version):
        env = {'QUERY_STRING': 'name=Share+Test+Name'}
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots/detail',
                                      environ=env,
                                      version=version)
        expected_s = fake_share.expected_snapshot(version=version, id=2)
        expected = {'snapshots': [expected_s['snapshot']]}

        res_dict = self.controller.detail(req)

        self.assertEqual(expected, res_dict)

    def test_snapshot_list_status_none(self):
        snapshots = [
            {
                'id': 3,
                'share_id': 'fakeshareid',
                'size': 1,
                'status': None,
                'name': 'displaysnapname',
                'description': 'displaysnapdesc',
            }
        ]
        self.mock_object(share_api.API, 'get_all_snapshots',
                         mock.Mock(return_value=snapshots))
        req = fakes.HTTPRequest.blank('/fake/snapshots')
        result = self.controller.index(req)
        self.assertEqual(1, len(result['snapshots']))
        self.assertEqual(snapshots[0]['id'], result['snapshots'][0]['id'])

    @ddt.data('2.0', '2.16', '2.17')
    def test_snapshot_update_display_name_and_description(self, version):
        snp = self.snp_example
        body = {"snapshot": snp}
        req = fakes.HTTPRequest.blank('/v2/fake/snapshot/1', version=version)

        res_dict = self.controller.update(req, 1, body)

        self.assertEqual(snp["display_name"], res_dict['snapshot']["name"])

        if (api_version.APIVersionRequest(version) <=
                api_version.APIVersionRequest('2.16')):
            self.assertNotIn('user_id', res_dict['snapshot'])
            self.assertNotIn('project_id', res_dict['snapshot'])
        else:
            self.assertIn('user_id', res_dict['snapshot'])
            self.assertIn('project_id', res_dict['snapshot'])

    def test_snapshot_update_display_description(self):
        snp = self.snp_example
        body = {"snapshot": snp}

        req = fakes.HTTPRequest.blank('/fake/snapshot/1')
        res_dict = self.controller.update(req, 1, body)

        self.assertEqual(snp["display_description"],
                         res_dict['snapshot']["description"])

    def test_snapshot_update_invalid_key(self):
        snp = self.snp_example
        body = {"snapshot": snp}

        req = fakes.HTTPRequest.blank('/v2/fake/snapshot/1')
        res_dict = self.controller.update(req, 1, body)

        self.assertNotEqual(snp["size"], res_dict['snapshot']["size"])

    def test_access_list(self):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])

        expected = []

        self.mock_object(share_api.API, 'get',
                         mock.Mock(return_value=share))
        self.mock_object(share_api.API, 'get_snapshot',
                         mock.Mock(return_value=snapshot))
        self.mock_object(share_api.API, 'snapshot_access_get_all',
                         mock.Mock(return_value=expected))

        id = 'fake_snap_id'
        req = fakes.HTTPRequest.blank('/v2/fake/snapshots/%s/action' % id,
                                      version='2.32')

        actual = self.controller.access_list(req, id)

        self.assertEqual(expected, actual['snapshot_access_list'])

    @ddt.data(('1.1.1.1', '2.32'),
              ('1.1.1.1', '2.38'),
              ('1001::1001', '2.38'))
    @ddt.unpack
    def test_allow_access(self, ip_address, version):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])

        access = {
            'id': 'fake_id',
            'access_type': 'ip',
            'access_to': ip_address,
            'state': 'new',
        }

        get = self.mock_object(share_api.API, 'get',
                               mock.Mock(return_value=share))
        get_snapshot = self.mock_object(share_api.API, 'get_snapshot',
                                        mock.Mock(return_value=snapshot))
        allow_access = self.mock_object(share_api.API, 'snapshot_allow_access',
                                        mock.Mock(return_value=access))
        body = {'allow_access': access}
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version=version)

        actual = self.controller.allow_access(req, snapshot['id'], body)

        self.assertEqual(access, actual['snapshot_access'])
        get.assert_called_once_with(utils.IsAMatcher(context.RequestContext),
                                    share['id'])
        get_snapshot.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot['id'])
        allow_access.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot,
            access['access_type'], access['access_to'])

    def test_allow_access_data_not_found_exception(self):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version='2.32')
        body = {}

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

    def test_allow_access_exists_exception(self):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version='2.32')
        access = {
            'id': 'fake_id',
            'access_type': 'ip',
            'access_to': '1.1.1.1',
            'state': 'new',
        }
        msg = "Share snapshot access exists."

        get = self.mock_object(share_api.API, 'get', mock.Mock(
            return_value=share))
        get_snapshot = self.mock_object(share_api.API, 'get_snapshot',
                                        mock.Mock(return_value=snapshot))
        allow_access = self.mock_object(
            share_api.API, 'snapshot_allow_access', mock.Mock(
                side_effect=exception.ShareSnapshotAccessExists(msg)))

        body = {'allow_access': access}

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

        get.assert_called_once_with(utils.IsAMatcher(context.RequestContext),
                                    share['id'])
        get_snapshot.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot['id'])
        allow_access.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot,
            access['access_type'], access['access_to'])

    def test_allow_access_share_without_mount_snap_support(self):
        share = db_utils.create_share(mount_snapshot_support=False)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])

        access = {
            'id': 'fake_id',
            'access_type': 'ip',
            'access_to': '1.1.1.1',
            'state': 'new',
        }

        get_snapshot = self.mock_object(share_api.API, 'get_snapshot',
                                        mock.Mock(return_value=snapshot))
        get = self.mock_object(share_api.API, 'get',
                               mock.Mock(return_value=share))

        body = {'allow_access': access}
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version='2.32')

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

        get.assert_called_once_with(utils.IsAMatcher(context.RequestContext),
                                    share['id'])
        get_snapshot.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot['id'])

    def test_allow_access_empty_parameters(self):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])

        access = {'id': 'fake_id',
                  'access_type': '',
                  'access_to': ''}

        body = {'allow_access': access}
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version='2.32')

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

    def test_deny_access(self):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])
        access = db_utils.create_snapshot_access(
            share_snapshot_id=snapshot['id'])

        get = self.mock_object(share_api.API, 'get',
                               mock.Mock(return_value=share))
        get_snapshot = self.mock_object(share_api.API, 'get_snapshot',
                                        mock.Mock(return_value=snapshot))
        access_get = self.mock_object(share_api.API, 'snapshot_access_get',
                                      mock.Mock(return_value=access))
        deny_access = self.mock_object(share_api.API, 'snapshot_deny_access')

        body = {'deny_access': {'access_id': access.id}}
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version='2.32')

        resp = self.controller.deny_access(req, snapshot['id'], body)

        self.assertEqual(202, resp.status_int)
        get.assert_called_once_with(utils.IsAMatcher(context.RequestContext),
                                    share['id'])
        get_snapshot.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot['id'])
        access_get.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext),
            body['deny_access']['access_id'])
        deny_access.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot, access)

    def test_deny_access_data_not_found_exception(self):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version='2.32')
        body = {}

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

    def test_deny_access_access_rule_not_found(self):
        share = db_utils.create_share(mount_snapshot_support=True)
        snapshot = db_utils.create_snapshot(
            status=constants.STATUS_AVAILABLE, share_id=share['id'])
        access = db_utils.create_snapshot_access(
            share_snapshot_id=snapshot['id'])
        wrong_access = {
            'access_type': 'fake_type',
            'access_to': 'fake_IP',
            'share_snapshot_id': 'fake_id'
        }

        get = self.mock_object(share_api.API, 'get',
                               mock.Mock(return_value=share))
        get_snapshot = self.mock_object(share_api.API, 'get_snapshot',
                                        mock.Mock(return_value=snapshot))
        access_get = self.mock_object(share_api.API, 'snapshot_access_get',
                                      mock.Mock(return_value=wrong_access))

        body = {'deny_access': {'access_id': access.id}}
        req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/action' % snapshot['id'], version='2.32')

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.deny_access, req, snapshot['id'],
                          body)
        get.assert_called_once_with(utils.IsAMatcher(context.RequestContext),
                                    share['id'])
        get_snapshot.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext), snapshot['id'])
        access_get.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext),
            body['deny_access']['access_id'])


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

    def setUp(self):
        super(ShareSnapshotAdminActionsAPITest, self).setUp()
        self.controller = share_snapshots.ShareSnapshotsController()
        self.flags(transport_url='rabbit://fake:fake@mqhost:5672')
        self.admin_context = context.RequestContext('admin', 'fake', True)
        self.member_context = context.RequestContext('fake', 'fake')

        self.resource_name = self.controller.resource_name
        self.manage_request = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/manage', use_admin_context=True,
            version=MIN_MANAGE_SNAPSHOT_API_VERSION)
        self.snapshot_id = 'fake'
        self.unmanage_request = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/unmanage' % self.snapshot_id,
            use_admin_context=True,
            version=MIN_MANAGE_SNAPSHOT_API_VERSION)

    def _get_context(self, role):
        return getattr(self, '%s_context' % role)

    def _setup_snapshot_data(self, snapshot=None, version='2.7'):
        if snapshot is None:
            share = db_utils.create_share()
            snapshot = db_utils.create_snapshot(
                status=constants.STATUS_AVAILABLE, share_id=share['id'])
        path = '/v2/fake/snapshots/%s/action' % snapshot['id']
        req = fakes.HTTPRequest.blank(path, script_name=path, version=version)
        return snapshot, req

    def _reset_status(self, ctxt, model, req, db_access_method,
                      valid_code, valid_status=None, body=None, version='2.7'):
        if float(version) > 2.6:
            action_name = 'reset_status'
        else:
            action_name = 'os-reset_status'
        if body is None:
            body = {action_name: {'status': constants.STATUS_ERROR}}
        req.method = 'POST'
        req.headers['content-type'] = 'application/json'
        req.headers['X-Openstack-Manila-Api-Version'] = version
        req.body = jsonutils.dumps(body).encode("utf-8")
        req.environ['manila.context'] = ctxt

        resp = req.get_response(fakes.app())

        # validate response code and model status
        self.assertEqual(valid_code, resp.status_int)

        actual_model = db_access_method(ctxt, model['id'])
        self.assertEqual(valid_status, actual_model['status'])

    @ddt.data(*fakes.fixture_reset_status_with_different_roles)
    @ddt.unpack
    def test_snapshot_reset_status_with_different_roles(self, role, valid_code,
                                                        valid_status, version):
        ctxt = self._get_context(role)
        snapshot, req = self._setup_snapshot_data(version=version)

        self._reset_status(ctxt, snapshot, req, db.share_snapshot_get,
                           valid_code, valid_status, version=version)

    @ddt.data(
        ({'os-reset_status': {'x-status': 'bad'}}, '2.6'),
        ({'reset_status': {'x-status': 'bad'}}, '2.7'),
        ({'os-reset_status': {'status': 'invalid'}}, '2.6'),
        ({'reset_status': {'status': 'invalid'}}, '2.7'),
    )
    @ddt.unpack
    def test_snapshot_invalid_reset_status_body(self, body, version):
        snapshot, req = self._setup_snapshot_data(version=version)

        self._reset_status(self.admin_context, snapshot, req,
                           db.share_snapshot_get, 400,
                           constants.STATUS_AVAILABLE, body, version=version)

    def _force_delete(self, ctxt, model, req, db_access_method, valid_code,
                      version='2.7'):
        if float(version) > 2.6:
            action_name = 'force_delete'
        else:
            action_name = 'os-force_delete'
        req.method = 'POST'
        req.headers['content-type'] = 'application/json'
        req.headers['X-Openstack-Manila-Api-Version'] = version
        req.body = jsonutils.dumps({action_name: {}}).encode("utf-8")
        req.environ['manila.context'] = ctxt

        resp = req.get_response(fakes.app())

        # Validate response
        self.assertEqual(valid_code, resp.status_int)

    @ddt.data(*fakes.fixture_force_delete_with_different_roles)
    @ddt.unpack
    def test_snapshot_force_delete_with_different_roles(self, role, resp_code,
                                                        version):
        ctxt = self._get_context(role)
        snapshot, req = self._setup_snapshot_data(version=version)

        self._force_delete(ctxt, snapshot, req, db.share_snapshot_get,
                           resp_code, version=version)

    def test_snapshot_force_delete_missing(self):
        ctxt = self._get_context('admin')
        snapshot, req = self._setup_snapshot_data(snapshot={'id': 'fake'})

        self._force_delete(ctxt, snapshot, req, db.share_snapshot_get, 404)

    @ddt.data(
        {},
        {'snapshots': {}},
        {'snapshot': get_fake_manage_body(share_id='xxxxxxxx')},
        {'snapshot': get_fake_manage_body(provider_location='xxxxxxxx')},
        {'snapshot': {'provider_location': {'x': 'y'}, 'share_id': 'xyzzy'}},
    )
    def test_snapshot_manage_invalid_body(self, body):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        self.assertRaises(webob.exc.HTTPUnprocessableEntity,
                          self.controller.manage,
                          self.manage_request,
                          body)
        self.mock_policy_check.assert_called_once_with(
            self.manage_request.environ['manila.context'],
            self.resource_name, 'manage_snapshot')

    @ddt.data(
        {'version': '2.12',
         'data': get_fake_manage_body(name='foo', display_description='bar')},
        {'version': '2.12',
         'data': get_fake_manage_body(display_name='foo', description='bar')},
        {'version': '2.17',
         'data': get_fake_manage_body(display_name='foo', description='bar')},
        {'version': '2.17',
         'data': get_fake_manage_body(name='foo', display_description='bar')},
    )
    @ddt.unpack
    def test_snapshot_manage(self, version, data):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        data['snapshot']['share_id'] = 'fake'
        data['snapshot']['provider_location'] = 'fake_volume_snapshot_id'
        data['snapshot']['driver_options'] = {}
        return_share = fake_share.fake_share(is_soft_deleted=False,
                                             id='fake')
        return_snapshot = fake_share.fake_snapshot(
            create_instance=True, id='fake_snap',
            provider_location='fake_volume_snapshot_id')
        self.mock_object(
            share_api.API, 'get', mock.Mock(
                return_value=return_share))
        self.mock_object(
            share_api.API, 'manage_snapshot', mock.Mock(
                return_value=return_snapshot))
        share_snapshot = {
            'share_id': 'fake',
            'provider_location': 'fake_volume_snapshot_id',
            'display_name': 'foo',
            'display_description': 'bar',
        }

        req = fakes.HTTPRequest.blank('/v2/fake/snapshots/manage',
                                      use_admin_context=True,
                                      version=version)

        actual_result = self.controller.manage(req, data)

        actual_snapshot = actual_result['snapshot']
        share_api.API.manage_snapshot.assert_called_once_with(
            mock.ANY, share_snapshot, data['snapshot']['driver_options'],
            share=return_share)
        self.assertEqual(return_snapshot['id'],
                         actual_result['snapshot']['id'])
        self.assertEqual('fake_volume_snapshot_id',
                         actual_result['snapshot']['provider_location'])

        if (api_version.APIVersionRequest(version) >=
                api_version.APIVersionRequest('2.17')):
            self.assertEqual(return_snapshot['user_id'],
                             actual_snapshot['user_id'])
            self.assertEqual(return_snapshot['project_id'],
                             actual_snapshot['project_id'])
        else:
            self.assertNotIn('user_id', actual_snapshot)
            self.assertNotIn('project_id', actual_snapshot)
        self.mock_policy_check.assert_called_once_with(
            req.environ['manila.context'], self.resource_name,
            'manage_snapshot')

    @ddt.data(exception.ShareNotFound(share_id='fake'),
              exception.ShareSnapshotNotFound(snapshot_id='fake'),
              exception.ManageInvalidShareSnapshot(reason='error'),
              exception.InvalidShare(reason='error'))
    def test_manage_exception(self, exception_type):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        body = get_fake_manage_body(
            share_id='fake', provider_location='fake_volume_snapshot_id',
            driver_options={})
        return_share = fake_share.fake_share(is_soft_deleted=False,
                                             id='fake')
        self.mock_object(
            share_api.API, 'get', mock.Mock(
                return_value=return_share))
        self.mock_object(
            share_api.API, 'manage_snapshot', mock.Mock(
                side_effect=exception_type))

        http_ex = webob.exc.HTTPNotFound

        if (isinstance(exception_type, exception.ManageInvalidShareSnapshot)
                or isinstance(exception_type, exception.InvalidShare)):
            http_ex = webob.exc.HTTPConflict

        self.assertRaises(http_ex,
                          self.controller.manage,
                          self.manage_request, body)
        self.mock_policy_check.assert_called_once_with(
            self.manage_request.environ['manila.context'],
            self.resource_name, 'manage_snapshot')

    def test_manage_share_has_been_soft_deleted(self):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        body = get_fake_manage_body(
            share_id='fake', provider_location='fake_volume_snapshot_id',
            driver_options={})
        return_share = fake_share.fake_share(is_soft_deleted=True,
                                             id='fake')
        self.mock_object(
            share_api.API, 'get', mock.Mock(
                return_value=return_share))

        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.manage,
                          self.manage_request, body)
        self.mock_policy_check.assert_called_once_with(
            self.manage_request.environ['manila.context'],
            self.resource_name, 'manage_snapshot')

    @ddt.data('2.6', '2.11')
    def test_manage_version_not_found(self, version):
        body = get_fake_manage_body(
            share_id='fake', provider_location='fake_volume_snapshot_id',
            driver_options={})
        fake_req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/manage', use_admin_context=True,
            version=version)

        self.assertRaises(exception.VersionNotFoundForAPIMethod,
                          self.controller.manage,
                          fake_req, body)

    def test_snapshot__unmanage(self):
        body = {}
        snapshot = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id',
                    'share_id': 'bar_id'}
        fake_req = fakes.HTTPRequest.blank('/v2/fake/snapshots/unmanage',
                                           use_admin_context=True,
                                           version='2.49')
        mock_unmanage = self.mock_object(self.controller, '_unmanage')

        self.controller.unmanage(fake_req, snapshot['id'], body)

        mock_unmanage.assert_called_once_with(fake_req, snapshot['id'], body,
                                              allow_dhss_true=True)

    def test_snapshot_unmanage_share_server(self):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        share = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id',
                 'share_server_id': 'fake_server_id'}
        self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
        snapshot = {'status': constants.STATUS_AVAILABLE, 'id': 'foo_id',
                    'share_id': 'bar_id'}
        self.mock_object(share_api.API, 'get_snapshot',
                         mock.Mock(return_value=snapshot))

        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.unmanage,
                          self.unmanage_request,
                          snapshot['id'])
        self.controller.share_api.get_snapshot.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], snapshot['id'])
        self.controller.share_api.get.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], share['id'])
        self.mock_policy_check.assert_called_once_with(
            self.unmanage_request.environ['manila.context'],
            self.resource_name, 'unmanage_snapshot')

    def test_snapshot_unmanage_replicated_snapshot(self):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        share = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id',
                 'has_replicas': True}
        self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
        snapshot = {'status': constants.STATUS_AVAILABLE, 'id': 'foo_id',
                    'share_id': 'bar_id'}
        self.mock_object(share_api.API, 'get_snapshot',
                         mock.Mock(return_value=snapshot))

        self.assertRaises(webob.exc.HTTPConflict,
                          self.controller.unmanage,
                          self.unmanage_request,
                          snapshot['id'])
        self.controller.share_api.get_snapshot.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], snapshot['id'])
        self.controller.share_api.get.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], share['id'])
        self.mock_policy_check.assert_called_once_with(
            self.unmanage_request.environ['manila.context'],
            self.resource_name, 'unmanage_snapshot')

    @ddt.data(*constants.TRANSITIONAL_STATUSES)
    def test_snapshot_unmanage_with_transitional_state(self, status):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        share = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id'}
        self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
        snapshot = {'status': status, 'id': 'foo_id', 'share_id': 'bar_id'}
        self.mock_object(
            self.controller.share_api, 'get_snapshot',
            mock.Mock(return_value=snapshot))
        self.assertRaises(
            webob.exc.HTTPForbidden,
            self.controller.unmanage, self.unmanage_request, snapshot['id'])

        self.controller.share_api.get_snapshot.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], snapshot['id'])
        self.controller.share_api.get.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], share['id'])
        self.mock_policy_check.assert_called_once_with(
            self.unmanage_request.environ['manila.context'],
            self.resource_name, 'unmanage_snapshot')

    def test_snapshot_unmanage(self):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        share = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id',
                 'host': 'fake_host'}
        self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
        snapshot = {'status': constants.STATUS_AVAILABLE, 'id': 'foo_id',
                    'share_id': 'bar_id'}
        self.mock_object(share_api.API, 'get_snapshot',
                         mock.Mock(return_value=snapshot))
        self.mock_object(share_api.API, 'unmanage_snapshot', mock.Mock())

        actual_result = self.controller.unmanage(self.unmanage_request,
                                                 snapshot['id'])

        self.assertEqual(202, actual_result.status_int)
        self.controller.share_api.get_snapshot.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], snapshot['id'])
        share_api.API.unmanage_snapshot.assert_called_once_with(
            mock.ANY, snapshot, 'fake_host')
        self.mock_policy_check.assert_called_once_with(
            self.unmanage_request.environ['manila.context'],
            self.resource_name, 'unmanage_snapshot')

    def test_unmanage_share_not_found(self):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        self.mock_object(
            share_api.API, 'get', mock.Mock(
                side_effect=exception.ShareNotFound(share_id='fake')))
        snapshot = {'status': constants.STATUS_AVAILABLE, 'id': 'foo_id',
                    'share_id': 'bar_id'}
        self.mock_object(share_api.API, 'get_snapshot',
                         mock.Mock(return_value=snapshot))
        self.mock_object(share_api.API, 'unmanage_snapshot', mock.Mock())

        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.unmanage,
                          self.unmanage_request, 'foo_id')
        self.mock_policy_check.assert_called_once_with(
            self.unmanage_request.environ['manila.context'],
            self.resource_name, 'unmanage_snapshot')

    def test_unmanage_snapshot_not_found(self):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        share = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id'}
        self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
        self.mock_object(
            share_api.API, 'get_snapshot', mock.Mock(
                side_effect=exception.ShareSnapshotNotFound(
                    snapshot_id='foo_id')))
        self.mock_object(share_api.API, 'unmanage_snapshot', mock.Mock())

        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.unmanage,
                          self.unmanage_request, 'foo_id')
        self.mock_policy_check.assert_called_once_with(
            self.unmanage_request.environ['manila.context'],
            self.resource_name, 'unmanage_snapshot')

    @ddt.data('2.6', '2.11')
    def test_unmanage_version_not_found(self, version):
        snapshot_id = 'fake'
        fake_req = fakes.HTTPRequest.blank(
            '/v2/fake/snapshots/%s/unmanage' % snapshot_id,
            use_admin_context=True,
            version=version)

        self.assertRaises(exception.VersionNotFoundForAPIMethod,
                          self.controller.unmanage,
                          fake_req, 'fake')

    def test_snapshot_unmanage_dhss_true_with_share_server(self):
        self.mock_policy_check = self.mock_object(
            policy, 'check_policy', mock.Mock(return_value=True))
        share = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id',
                 'host': 'fake_host',
                 'share_server_id': 'fake'}
        mock_get = self.mock_object(share_api.API, 'get',
                                    mock.Mock(return_value=share))
        snapshot = {'status': constants.STATUS_AVAILABLE, 'id': 'bar_id',
                    'share_id': 'bar_id'}
        self.mock_object(share_api.API, 'get_snapshot',
                         mock.Mock(return_value=snapshot))
        self.mock_object(share_api.API, 'unmanage_snapshot')

        actual_result = self.controller._unmanage(self.unmanage_request,
                                                  snapshot['id'],
                                                  allow_dhss_true=True)

        self.assertEqual(202, actual_result.status_int)
        self.controller.share_api.get_snapshot.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], snapshot['id'])
        share_api.API.unmanage_snapshot.assert_called_once_with(
            mock.ANY, snapshot, 'fake_host')
        mock_get.assert_called_once_with(
            self.unmanage_request.environ['manila.context'], snapshot['id']
        )
        self.mock_policy_check.assert_called_once_with(
            self.unmanage_request.environ['manila.context'],
            self.resource_name, 'unmanage_snapshot')
