# Copyright (C) 2016 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.

"""Tests for group code."""

from http import HTTPStatus
from unittest import mock

import ddt
import webob

from cinder.api import microversions as mv
from cinder.api.v3 import groups as v3_groups
from cinder import context
from cinder import db
from cinder import exception
import cinder.group
from cinder import objects
from cinder.objects import fields
from cinder.tests.unit.api import fakes
from cinder.tests.unit.api.v3 import fakes as v3_fakes
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import test
from cinder.tests.unit import utils
from cinder.volume import api as volume_api

INVALID_GROUP_REPLICATION = mv.get_prior_version(mv.GROUP_REPLICATION)


@ddt.ddt
class GroupsAPITestCase(test.TestCase):
    """Test Case for groups API."""

    def setUp(self):
        super(GroupsAPITestCase, self).setUp()
        self.controller = v3_groups.GroupsController()
        self.group_api = cinder.group.API()
        self.ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
                                           auth_token=True,
                                           is_admin=True)
        self.user_ctxt = context.RequestContext(
            fake.USER_ID, fake.PROJECT_ID, auth_token=True)
        self.volume_type1 = self._create_volume_type(id=fake.VOLUME_TYPE_ID)
        self.group1 = self._create_group()
        self.group2 = self._create_group()
        self.group3 = self._create_group(ctxt=self.user_ctxt)
        self.addCleanup(self._cleanup)

    def _cleanup(self):
        self.group1.destroy()
        self.group2.destroy()
        self.group3.destroy()
        db.volume_type_destroy(self.ctxt, self.volume_type1.id)

    def _create_group(
            self,
            ctxt=None,
            name='test_group',
            description='this is a test group',
            group_type_id=fake.GROUP_TYPE_ID,
            volume_type_ids=[fake.VOLUME_TYPE_ID],
            availability_zone='az1',
            host='fakehost',
            status=fields.GroupStatus.CREATING,
            replication_status=fields.ReplicationStatus.DISABLED,
            **kwargs):
        """Create a group object."""
        ctxt = ctxt or self.ctxt
        group = objects.Group(ctxt)
        group.user_id = fake.USER_ID
        group.project_id = fake.PROJECT_ID
        group.availability_zone = availability_zone
        group.name = name
        group.description = description
        group.group_type_id = group_type_id
        group.volume_type_ids = volume_type_ids
        group.host = host
        group.status = status
        group.replication_status = replication_status
        group.update(kwargs)
        group.create()
        return group

    def _create_volume_type(
            self,
            ctxt=None,
            id=fake.VOLUME_TYPE_ID,
            name='test_volume_type',
            description='this is a test volume type',
            extra_specs={"test_key": "test_val"},
            testcase_instance=None,
            **kwargs):
        """Create a volume type."""
        ctxt = ctxt or self.ctxt
        vol_type = utils.create_volume_type(
            ctxt,
            testcase_instance=testcase_instance,
            id=id,
            name=name,
            description=description,
            extra_specs=extra_specs,
            **kwargs)
        return vol_type

    @mock.patch('cinder.objects.volume_type.VolumeTypeList.get_all_by_group')
    @mock.patch('cinder.objects.volume.VolumeList.get_all_by_generic_group')
    def test_show_group(self, mock_vol_get_all_by_group,
                        mock_vol_type_get_all_by_group):
        volume_objs = [objects.Volume(context=self.ctxt, id=i)
                       for i in [fake.VOLUME_ID]]
        volumes = objects.VolumeList(context=self.ctxt, objects=volume_objs)
        mock_vol_get_all_by_group.return_value = volumes

        vol_type_objs = [objects.VolumeType(context=self.ctxt, id=i)
                         for i in [fake.VOLUME_TYPE_ID]]
        vol_types = objects.VolumeTypeList(context=self.ctxt,
                                           objects=vol_type_objs)
        mock_vol_type_get_all_by_group.return_value = vol_types

        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        res_dict = self.controller.show(req, self.group1.id)

        self.assertEqual(1, len(res_dict))
        self.assertEqual('az1',
                         res_dict['group']['availability_zone'])
        self.assertEqual('this is a test group',
                         res_dict['group']['description'])
        self.assertEqual('test_group',
                         res_dict['group']['name'])
        self.assertEqual('creating',
                         res_dict['group']['status'])
        self.assertEqual([fake.VOLUME_TYPE_ID],
                         res_dict['group']['volume_types'])

    @ddt.data((mv.get_prior_version(mv.GROUP_VOLUME_LIST), False),
              (mv.get_prior_version(mv.GROUP_VOLUME_LIST), True),
              (mv.GROUP_VOLUME_LIST, False),
              (mv.GROUP_VOLUME_LIST, True))
    @ddt.unpack
    @mock.patch('cinder.objects.volume_type.VolumeTypeList.get_all_by_group')
    @mock.patch('cinder.objects.volume.VolumeList.get_all_by_generic_group')
    def test_list_group_with_list_volume(self, version, has_list_volume,
                                         mock_vol_get_all_by_group,
                                         mock_vol_type_get_all_by_group):
        volume_objs = [objects.Volume(context=self.ctxt, id=i)
                       for i in [fake.VOLUME_ID]]
        volumes = objects.VolumeList(context=self.ctxt, objects=volume_objs)
        mock_vol_get_all_by_group.return_value = volumes

        vol_type_objs = [objects.VolumeType(context=self.ctxt, id=i)
                         for i in [fake.VOLUME_TYPE_ID]]
        vol_types = objects.VolumeTypeList(context=self.ctxt,
                                           objects=vol_type_objs)
        mock_vol_type_get_all_by_group.return_value = vol_types

        if has_list_volume:
            req = fakes.HTTPRequest.blank(
                '/v3/%s/groups/detail?list_volume=True' % fake.PROJECT_ID,
                version=version)
        else:
            req = fakes.HTTPRequest.blank('/v3/%s/groups/detail' %
                                          fake.PROJECT_ID,
                                          version=version)
        res_dict = self.controller.detail(req)

        # If the microversion >= 3.25 and "list_volume=True", "volumes" should
        # be contained in the response body. Else, "volumes" should not be
        # contained in the response body.
        self.assertEqual(3, len(res_dict['groups']))
        if (version, has_list_volume) == (mv.GROUP_VOLUME_LIST, True):
            self.assertEqual([fake.VOLUME_ID],
                             res_dict['groups'][0]['volumes'])
        else:
            self.assertNotIn('volumes', res_dict['groups'][0])

        # "volumes" should not be contained in the response body when list
        # groups without detail.
        res_dict = self.controller.index(req)
        self.assertNotIn('volumes', res_dict['groups'][0])

    @mock.patch('cinder.objects.volume_type.VolumeTypeList.get_all_by_group')
    @mock.patch('cinder.objects.volume.VolumeList.get_all_by_generic_group')
    def test_show_group_with_list_volume(self, mock_vol_get_all_by_group,
                                         mock_vol_type_get_all_by_group):
        volume_objs = [objects.Volume(context=self.ctxt, id=i)
                       for i in [fake.VOLUME_ID]]
        volumes = objects.VolumeList(context=self.ctxt, objects=volume_objs)
        mock_vol_get_all_by_group.return_value = volumes

        vol_type_objs = [objects.VolumeType(context=self.ctxt, id=i)
                         for i in [fake.VOLUME_TYPE_ID]]
        vol_types = objects.VolumeTypeList(context=self.ctxt,
                                           objects=vol_type_objs)
        mock_vol_type_get_all_by_group.return_value = vol_types

        # If the microversion >= 3.25 and "list_volume=True", "volumes" should
        # be contained in the response body.
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s?list_volume=True' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME_LIST)
        res_dict = self.controller.show(req, self.group1.id)
        self.assertEqual(1, len(res_dict))
        self.assertEqual([fake.VOLUME_ID],
                         res_dict['group']['volumes'])

        # If the microversion >= 3.25 but "list_volume" is missing, "volumes"
        # should not be contained in the response body.
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME_LIST)
        res_dict = self.controller.show(req, self.group1.id)
        self.assertEqual(1, len(res_dict))
        self.assertNotIn('volumes', res_dict['group'])

        # If the microversion < 3.25, "volumes" should not be contained in the
        # response body.
        req = fakes.HTTPRequest.blank(
            '/v3/%s/groups/%s?list_volume=True' %
            (fake.PROJECT_ID, self.group1.id),
            version=mv.get_prior_version(mv.GROUP_VOLUME_LIST))
        res_dict = self.controller.show(req, self.group1.id)
        self.assertEqual(1, len(res_dict))
        self.assertNotIn('volumes', res_dict['group'])

    def test_show_group_with_group_NotFound(self):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s' %
                                      (fake.PROJECT_ID,
                                       fake.WILL_NOT_BE_FOUND_ID),
                                      version=mv.GROUP_VOLUME)
        self.assertRaises(exception.GroupNotFound, self.controller.show,
                          req, fake.WILL_NOT_BE_FOUND_ID)

    @ddt.data(mv.get_prior_version(mv.RESOURCE_FILTER),
              mv.RESOURCE_FILTER, mv.LIKE_FILTER)
    @mock.patch('cinder.api.common.reject_invalid_filters')
    def test_group_list_with_general_filter(self, version, mock_update):
        url = '/v3/%s/groups' % fake.PROJECT_ID
        req = fakes.HTTPRequest.blank(url,
                                      version=version,
                                      use_admin_context=False)
        self.controller.index(req)

        if version != mv.get_prior_version(mv.RESOURCE_FILTER):
            support_like = True if version == mv.LIKE_FILTER else False
            mock_update.assert_called_once_with(req.environ['cinder.context'],
                                                mock.ANY, 'group',
                                                support_like)

    def test_list_groups_json(self):
        self.group2.group_type_id = fake.GROUP_TYPE2_ID
        # TODO(geguileo): One `volume_type_ids` gets sorted out make proper
        # changes here
        # self.group2.volume_type_ids = [fake.VOLUME_TYPE2_ID]

        self.group2.save()

        self.group3.group_type_id = fake.GROUP_TYPE3_ID
        # TODO(geguileo): One `volume_type_ids` gets sorted out make proper
        # changes here
        # self.group3.volume_type_ids = [fake.VOLUME_TYPE3_ID]
        self.group3.save()

        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        res_dict = self.controller.index(req)

        self.assertEqual(1, len(res_dict))
        self.assertEqual(self.group3.id,
                         res_dict['groups'][0]['id'])
        self.assertEqual('test_group',
                         res_dict['groups'][0]['name'])
        self.assertEqual(self.group2.id,
                         res_dict['groups'][1]['id'])
        self.assertEqual('test_group',
                         res_dict['groups'][1]['name'])
        self.assertEqual(self.group1.id,
                         res_dict['groups'][2]['id'])
        self.assertEqual('test_group',
                         res_dict['groups'][2]['name'])

    @ddt.data(False, True)
    def test_list_groups_with_limit(self, is_detail):
        url = '/v3/%s/groups?limit=1' % fake.PROJECT_ID
        if is_detail:
            url = '/v3/%s/groups/detail?limit=1' % fake.PROJECT_ID
        req = fakes.HTTPRequest.blank(url, version=mv.GROUP_VOLUME)

        if is_detail:
            res_dict = self.controller.detail(req)
        else:
            res_dict = self.controller.index(req)

        self.assertEqual(2, len(res_dict))
        self.assertEqual(1, len(res_dict['groups']))
        self.assertEqual(self.group3.id,
                         res_dict['groups'][0]['id'])
        next_link = (
            'http://localhost/v3/%s/groups?limit='
            '1&marker=%s' %
            (fake.PROJECT_ID, res_dict['groups'][0]['id']))
        self.assertEqual(next_link,
                         res_dict['group_links'][0]['href'])
        if is_detail:
            self.assertIn('description', res_dict['groups'][0].keys())

    @ddt.data(False, True)
    def test_list_groups_with_offset(self, is_detail):
        url = '/v3/%s/groups?offset=1' % fake.PROJECT_ID
        if is_detail:
            url = '/v3/%s/groups/detail?offset=1' % fake.PROJECT_ID
        req = fakes.HTTPRequest.blank(url, version=mv.GROUP_VOLUME)
        res_dict = self.controller.index(req)

        self.assertEqual(1, len(res_dict))
        self.assertEqual(2, len(res_dict['groups']))
        self.assertEqual(self.group2.id,
                         res_dict['groups'][0]['id'])
        self.assertEqual(self.group1.id,
                         res_dict['groups'][1]['id'])

    @ddt.data(False, True)
    def test_list_groups_with_offset_out_of_range(self, is_detail):
        url = ('/v3/%s/groups?offset=234523423455454' %
               fake.PROJECT_ID)
        if is_detail:
            url = ('/v3/%s/groups/detail?offset=234523423455454' %
                   fake.PROJECT_ID)
        req = fakes.HTTPRequest.blank(url, version=mv.GROUP_VOLUME)
        if is_detail:
            self.assertRaises(webob.exc.HTTPBadRequest, self.controller.detail,
                              req)
        else:
            self.assertRaises(webob.exc.HTTPBadRequest, self.controller.index,
                              req)

    @ddt.data(False, True)
    def test_list_groups_with_limit_and_offset(self, is_detail):
        url = '/v3/%s/groups?limit=2&offset=1' % fake.PROJECT_ID
        if is_detail:
            url = ('/v3/%s/groups/detail?limit=2&offset=1' %
                   fake.PROJECT_ID)
        req = fakes.HTTPRequest.blank(url, version=mv.GROUP_VOLUME)

        if is_detail:
            res_dict = self.controller.detail(req)
        else:
            res_dict = self.controller.index(req)

        self.assertEqual(2, len(res_dict))
        self.assertEqual(2, len(res_dict['groups']))
        self.assertEqual(self.group2.id,
                         res_dict['groups'][0]['id'])
        self.assertEqual(self.group1.id,
                         res_dict['groups'][1]['id'])
        if is_detail:
            self.assertIn('description', res_dict['groups'][0].keys())

    @ddt.data(False, True)
    def test_list_groups_with_filter(self, is_detail):
        # Create a group with user context
        url = ('/v3/%s/groups?'
               'all_tenants=True&id=%s') % (fake.PROJECT_ID,
                                            self.group3.id)
        if is_detail:
            url = ('/v3/%s/groups/detail?'
                   'all_tenants=True&id=%s') % (fake.PROJECT_ID,
                                                self.group3.id)
        req = fakes.HTTPRequest.blank(url, version=mv.GROUP_VOLUME,
                                      use_admin_context=True)

        if is_detail:
            res_dict = self.controller.detail(req)
        else:
            res_dict = self.controller.index(req)

        self.assertEqual(1, len(res_dict))
        self.assertEqual(1, len(res_dict['groups']))
        self.assertEqual(self.group3.id,
                         res_dict['groups'][0]['id'])
        if is_detail:
            self.assertIn('description', res_dict['groups'][0].keys())

    @ddt.data(False, True)
    def test_list_groups_with_sort(self, is_detail):
        url = '/v3/%s/groups?sort=id:asc' % fake.PROJECT_ID
        if is_detail:
            url = ('/v3/%s/groups/detail?sort=id:asc' %
                   fake.PROJECT_ID)
        req = fakes.HTTPRequest.blank(url, version=mv.GROUP_VOLUME)
        expect_result = [self.group1.id, self.group2.id,
                         self.group3.id]
        expect_result.sort()

        if is_detail:
            res_dict = self.controller.detail(req)
        else:
            res_dict = self.controller.index(req)

        self.assertEqual(1, len(res_dict))
        self.assertEqual(3, len(res_dict['groups']))
        self.assertEqual(expect_result[0],
                         res_dict['groups'][0]['id'])
        self.assertEqual(expect_result[1],
                         res_dict['groups'][1]['id'])
        self.assertEqual(expect_result[2],
                         res_dict['groups'][2]['id'])
        if is_detail:
            self.assertIn('description', res_dict['groups'][0].keys())

    @mock.patch('cinder.objects.volume_type.VolumeTypeList.get_all_by_group')
    def test_list_groups_detail_json(self, mock_vol_type_get_all_by_group):
        volume_type_ids = [fake.VOLUME_TYPE_ID, fake.VOLUME_TYPE2_ID]
        vol_type_objs = [objects.VolumeType(context=self.ctxt, id=i)
                         for i in volume_type_ids]
        vol_types = objects.VolumeTypeList(context=self.ctxt,
                                           objects=vol_type_objs)
        mock_vol_type_get_all_by_group.return_value = vol_types

        # TODO(geguileo): One `volume_type_ids` gets sorted out make proper
        # changes here
        # self.group1.volume_type_ids = volume_type_ids
        # self.group1.save()
        # self.group2.volume_type_ids = volume_type_ids
        # self.group2.save()
        # self.group3.volume_type_ids = volume_type_ids
        # self.group3.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/detail' %
                                      fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        res_dict = self.controller.detail(req)

        self.assertEqual(1, len(res_dict))
        index = 0
        for group in [self.group3, self.group2, self.group1]:
            self.assertEqual(group.id,
                             res_dict['groups'][index]['id'])
            self.assertEqual([fake.VOLUME_TYPE_ID, fake.VOLUME_TYPE2_ID],
                             res_dict['groups'][index]['volume_types'])
            self.assertEqual('test_group',
                             res_dict['groups'][index]['name'])
            self.assertTrue({'availability_zone', 'description',
                             'status'}.issubset(
                                 set(res_dict['groups'][index].keys())))
            index += 1

    @ddt.data(False, True)
    def test_create_group_json(self, use_group_type_name):
        # Create volume types and group type
        vol_type = 'test'
        vol_type_id = db.volume_type_create(
            self.ctxt,
            {'name': vol_type, 'extra_specs': {}}).get('id')
        grp_type_name = 'test_grp_type'
        grp_type = db.group_type_create(
            self.ctxt,
            {'name': grp_type_name, 'group_specs': {}}).get('id')
        if use_group_type_name:
            grp_type = grp_type_name
        body = {"group": {"name": "group1",
                          "volume_types": [vol_type_id],
                          "group_type": grp_type,
                          "description":
                          "Group 1", }}
        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        res_dict = self.controller.create(req, body=body)

        self.assertEqual(1, len(res_dict))
        self.assertIn('id', res_dict['group'])

        group_id = res_dict['group']['id']
        objects.Group.get_by_id(self.ctxt, group_id)

    def test_create_group_with_no_body(self):
        # omit body from the request
        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        self.assertRaises(exception.ValidationError, self.controller.create,
                          req, body=None)

    @ddt.data(("", webob.exc.HTTPBadRequest),
              ("    ", exception.InvalidInput),
              ("a" * 256, exception.InvalidInput))
    @ddt.unpack
    def test_create_group_with_invalid_availability_zone(
            self, az_name, exceptions):
        vol_type = 'test'
        vol_type_id = db.volume_type_create(
            self.ctxt,
            {'name': vol_type, 'extra_specs': {}}).get('id')
        grp_type_name = 'test_grp_type'
        grp_type = db.group_type_create(
            self.ctxt,
            {'name': grp_type_name, 'group_specs': {}}).get('id')
        body = {"group": {"name": "group1",
                          "volume_types": [vol_type_id],
                          "group_type": grp_type,
                          "availability_zone": az_name}}
        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        self.assertRaises(exceptions, self.controller.create,
                          req, body=body)

    def test_delete_group_available(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": False}}
        res_dict = self.controller.delete_group(
            req, self.group1.id, body=body)

        group = objects.Group.get_by_id(
            self.ctxt, self.group1.id)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        self.assertEqual(fields.GroupStatus.DELETING, group.status)

    def test_delete_group_available_no_delete_volumes(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": False}}
        res_dict = self.controller.delete_group(
            req, self.group1.id, body=body)

        group = objects.Group.get_by_id(
            self.ctxt, self.group1.id)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        self.assertEqual(fields.GroupStatus.DELETING,
                         group.status)

    def test_delete_group_with_group_NotFound(self):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID,
                                       fake.WILL_NOT_BE_FOUND_ID),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": False}}
        self.assertRaises(exception.GroupNotFound,
                          self.controller.delete_group,
                          req, fake.WILL_NOT_BE_FOUND_ID, body=body)

    def test_delete_group_with_invalid_group(self):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID,
                                       self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": False}}
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.delete_group,
                          req, self.group1.id, body=body)

    def test_delete_group_invalid_delete_volumes(self):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID,
                                       self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": True}}
        res_dict = self.controller.delete_group(
            req, self.group1.id, body=body)

        group = objects.Group.get_by_id(
            self.ctxt, self.group1.id)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        self.assertEqual(fields.GroupStatus.DELETING, group.status)

    def test_delete_group_no_host(self):
        self.group1.host = None
        self.group1.status = fields.GroupStatus.ERROR
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID,
                                       self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": True}}
        res_dict = self.controller.delete_group(
            req, self.group1.id, body=body)

        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        group = objects.Group.get_by_id(
            context.get_admin_context(read_deleted='yes'),
            self.group1.id)
        self.assertEqual(fields.GroupStatus.DELETED, group.status)
        self.assertIsNone(group.host)

    @mock.patch('cinder.quota.GROUP_QUOTAS.reserve',
                return_value='reservations')
    @mock.patch('cinder.quota.GROUP_QUOTAS.commit')
    def test_create_delete_group_update_quota(self, mock_commit, mock_reserve):
        name = 'mygroup'
        description = 'group 1'
        grp_type = {'id': fake.GROUP_TYPE_ID, 'name': 'group_type'}
        fake_type = {'id': fake.VOLUME_TYPE_ID, 'name': 'fake_type'}
        self.mock_object(db, 'volume_types_get_by_name_or_id',
                         return_value=[fake_type])
        self.mock_object(db, 'group_type_get', return_value=grp_type)
        self.mock_object(self.group_api, '_cast_create_group')
        self.mock_object(self.group_api, 'update_quota')
        group = self.group_api.create(self.ctxt, name, description,
                                      grp_type['id'], [fake_type['id']])
        # Verify that quota reservation and commit was called
        mock_reserve.assert_called_once_with(self.ctxt,
                                             project_id=self.ctxt.project_id,
                                             groups=1)
        mock_commit.assert_called_once_with(self.ctxt, 'reservations')

        self.assertEqual(fields.GroupStatus.CREATING, group.status)
        self.assertIsNone(group.host)
        group.status = fields.GroupStatus.ERROR
        self.group_api.delete(self.ctxt, group)

        self.group_api.update_quota.assert_called_once_with(
            self.ctxt, group, -1, self.ctxt.project_id)
        group = objects.Group.get_by_id(
            context.get_admin_context(read_deleted='yes'),
            group.id)
        self.assertEqual(fields.GroupStatus.DELETED, group.status)

    @mock.patch('cinder.group.api.API.create')
    def test_create_group_failed_exceeded_quota(self, mock_group_create):
        mock_group_create.side_effect = exception.GroupLimitExceeded(allowed=1)
        name = 'group1'
        body = {"group": {"group_type": fake.GROUP_TYPE_ID,
                          "volume_types": [fake.VOLUME_TYPE_ID],
                          "name": name,
                          "description":
                          "Group 1", }}
        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        ex = self.assertRaises(exception.GroupLimitExceeded,
                               self.controller.create,
                               req, body=body)
        self.assertEqual(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, ex.code)

    def test_delete_group_with_invalid_body(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"invalid_request_element": {"delete-volumes": False}}
        self.assertRaises(exception.ValidationError,
                          self.controller.delete_group,
                          req, self.group1.id, body=body)

    def test_delete_group_with_invalid_delete_volumes_value_in_body(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": "abcd"}}
        self.assertRaises(exception.ValidationError,
                          self.controller.delete_group,
                          req, self.group1.id, body=body)

    def test_delete_group_with_empty_delete_volumes_value_in_body(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": ""}}
        self.assertRaises(exception.ValidationError,
                          self.controller.delete_group,
                          req, self.group1.id, body=body)

    def test_delete_group_with_group_snapshot(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        g_snapshot = utils.create_group_snapshot(self.ctxt, self.group1.id)

        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": True}}
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.delete_group,
                          req, self.group1.id, body=body)

        g_snapshot.destroy()

        res_dict = self.controller.delete_group(
            req, self.group1.id, body=body)

        group = objects.Group.get_by_id(
            self.ctxt, self.group1.id)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        self.assertEqual(fields.GroupStatus.DELETING, group.status)

    def test_delete_group_delete_volumes(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        vol = utils.create_volume(self.ctxt, group_id=self.group1.id)
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": True}}
        res_dict = self.controller.delete_group(
            req, self.group1.id, body=body)

        group = objects.Group.get_by_id(
            self.ctxt, self.group1.id)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        self.assertEqual(fields.GroupStatus.DELETING, group.status)

        vol.destroy()

    def test_delete_group_delete_volumes_with_attached_volumes(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        vol = utils.create_volume(self.ctxt, group_id=self.group1.id,
                                  attach_status='attached')
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": True}}
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.delete_group,
                          req, self.group1.id, body=body)

        vol.destroy()

    def test_delete_group_delete_volumes_with_snapshots(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        vol = utils.create_volume(self.ctxt, group_id=self.group1.id)
        utils.create_snapshot(self.ctxt, vol.id)
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": True}}
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.delete_group,
                          req, self.group1.id, body=body)

        vol.destroy()

    def test_delete_group_delete_volumes_with_deleted_snapshots(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        vol = utils.create_volume(self.ctxt, group_id=self.group1.id)
        utils.create_snapshot(self.ctxt, vol.id,
                              status=fields.SnapshotStatus.DELETED,
                              deleted=True)
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"delete": {"delete-volumes": True}}
        res_dict = self.controller.delete_group(
            req, self.group1.id, body=body)

        group = objects.Group.get_by_id(
            self.ctxt, self.group1.id)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        self.assertEqual(fields.GroupStatus.DELETING, group.status)

        vol.destroy()

    def test_create_group_failed_no_group_type(self):
        name = 'group1'
        body = {"group": {"volume_types": [fake.VOLUME_TYPE_ID],
                          "name": name,
                          "description":
                          "Group 1", }}
        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        self.assertRaises(exception.ValidationError,
                          self.controller.create,
                          req, body=body)

    @ddt.data(None, "", "    ", "a" * 256)
    def test_create_group_failed_invalid_group_type(self, group_type):
        name = 'group1'
        body = {"group": {"volume_types": [fake.VOLUME_TYPE_ID],
                          "name": name,
                          "description": "Group 1",
                          "group_type": group_type}}
        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        self.assertRaises(exception.ValidationError, self.controller.create,
                          req, body=body)

    def test_create_group_failed_no_volume_types(self):
        name = 'group1'
        body = {"group": {"group_type": fake.GROUP_TYPE_ID,
                          "name": name,
                          "description":
                          "Group 1", }}
        req = fakes.HTTPRequest.blank('/v3/%s/groups' % fake.PROJECT_ID,
                                      version=mv.GROUP_VOLUME)
        self.assertRaises(exception.ValidationError,
                          self.controller.create,
                          req, body=body)

    def test_update_group_success(self):
        volume_type_id = fake.VOLUME_TYPE_ID
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.host = 'test_host'
        # TODO(geguileo): One `volume_type_ids` gets sorted out make proper
        # changes here
        # self.group1.volume_type_ids = [volume_type_id]
        self.group1.save()

        remove_volume = utils.create_volume(
            self.ctxt,
            volume_type_id=volume_type_id,
            group_id=self.group1.id)
        remove_volume2 = utils.create_volume(
            self.ctxt,
            volume_type_id=volume_type_id,
            group_id=self.group1.id,
            status='error')
        remove_volume3 = utils.create_volume(
            self.ctxt,
            volume_type_id=volume_type_id,
            group_id=self.group1.id,
            status='error_deleting')

        self.assertEqual(fields.GroupStatus.AVAILABLE,
                         self.group1.status)

        group_volumes = db.volume_get_all_by_generic_group(
            self.ctxt.elevated(),
            self.group1.id)
        group_vol_ids = [group_vol['id'] for group_vol in group_volumes]
        self.assertIn(remove_volume.id, group_vol_ids)
        self.assertIn(remove_volume2.id, group_vol_ids)
        self.assertIn(remove_volume3.id, group_vol_ids)

        add_volume = utils.create_volume(
            self.ctxt,
            volume_type_id=volume_type_id)
        add_volume2 = utils.create_volume(
            self.ctxt,
            volume_type_id=volume_type_id)
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        name = 'newgroup'
        description = 'New Group Description'
        add_volumes = add_volume.id + "," + add_volume2.id
        remove_volumes = ','.join(
            [remove_volume.id, remove_volume2.id, remove_volume3.id])
        body = {"group": {"name": name,
                          "description": description,
                          "add_volumes": add_volumes,
                          "remove_volumes": remove_volumes, }}
        res_dict = self.controller.update(
            req, self.group1.id, body=body)

        group = objects.Group.get_by_id(
            self.ctxt, self.group1.id)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)
        self.assertEqual(fields.GroupStatus.UPDATING,
                         group.status)

        remove_volume.destroy()
        remove_volume2.destroy()
        remove_volume3.destroy()
        add_volume.destroy()
        add_volume2.destroy()

    @ddt.data(fields.GroupStatus.CREATING, fields.GroupStatus.UPDATING)
    def test_update_group_any_state(self, status):
        self.group1.status = status
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"group": {"name": "new name",
                          "description": "new description",
                          "add_volumes": None,
                          "remove_volumes": None, }}

        res_dict = self.controller.update(
            req, self.group1.id, body=body)
        self.assertEqual(HTTPStatus.ACCEPTED, res_dict.status_int)

        group = objects.Group.get_by_id(self.ctxt, self.group1.id)
        self.assertEqual("new name", group.name)
        self.assertEqual("new description", group.description)

    def test_update_group_add_volume_not_found(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"group": {"name": None,
                          "description": None,
                          "add_volumes": "fake-volume-uuid",
                          "remove_volumes": None, }}

        self.assertRaises(exception.InvalidVolume,
                          self.controller.update,
                          req, self.group1.id, body=body)

    def test_update_group_remove_volume_not_found(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"group": {"name": None,
                          "description": "new description",
                          "add_volumes": None,
                          "remove_volumes": "fake-volume-uuid", }}

        self.assertRaises(exception.InvalidVolume,
                          self.controller.update,
                          req, self.group1.id, body=body)

    def test_update_group_empty_parameters(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        body = {"group": {"name": None,
                          "description": None,
                          "add_volumes": None,
                          "remove_volumes": None, }}

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.update,
                          req, self.group1.id, body=body)

    def test_update_group_add_volume_invalid_state(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        add_volume = utils.create_volume(
            self.ctxt,
            volume_type_id=fake.VOLUME_TYPE_ID,
            status='wrong_status')
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        add_volumes = add_volume.id
        body = {"group": {"name": "group1",
                          "description": "",
                          "add_volumes": add_volumes,
                          "remove_volumes": None, }}

        self.assertRaises(exception.InvalidVolume,
                          self.controller.update,
                          req, self.group1.id, body=body)

        add_volume.destroy()

    def test_update_group_add_volume_invalid_volume_type(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        wrong_type = fake.VOLUME_TYPE2_ID
        add_volume = utils.create_volume(
            self.ctxt,
            volume_type_id=wrong_type)
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        add_volumes = add_volume.id
        body = {"group": {"name": "group1",
                          "description": "",
                          "add_volumes": add_volumes,
                          "remove_volumes": None, }}

        self.assertRaises(exception.InvalidVolume,
                          self.controller.update,
                          req, self.group1.id, body=body)

        add_volume.destroy()

    def test_update_group_add_volume_already_in_group(self):
        self.group1.status = fields.GroupStatus.AVAILABLE
        self.group1.save()
        add_volume = utils.create_volume(
            self.ctxt,
            group_id=fake.GROUP2_ID)
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)
        add_volumes = add_volume.id
        body = {"group": {"name": "group1",
                          "description": "",
                          "add_volumes": add_volumes,
                          "remove_volumes": None, }}

        self.assertRaises(exception.InvalidVolume,
                          self.controller.update,
                          req, self.group1.id, body=body)

        add_volume.destroy()

    @ddt.data(fields.GroupStatus.CREATING, fields.GroupStatus.UPDATING)
    def test_update_group_invalid_state(self, status):
        self.group1.status = status
        add_volume = utils.create_volume(
            self.ctxt,
            volume_type_id=self.volume_type1.id,
            host=self.group1.host)
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
                                      (fake.PROJECT_ID, self.group1.id),
                                      version=mv.GROUP_VOLUME)

        body = {"group": {"name": "new name",
                          "description": None,
                          "add_volumes": add_volume.id,
                          "remove_volumes": None, }}

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.update,
                          req, self.group1.id, body=body)

        vol = objects.Volume.get_by_id(self.ctxt, add_volume.id)
        self.assertEqual(add_volume.status, vol.status)
        add_volume.destroy()

    @ddt.data((mv.GROUP_TYPE, 'fake_group_001',
               fields.GroupStatus.AVAILABLE,
               exception.VersionNotFoundForAPIMethod),
              (mv.GROUP_SNAPSHOT_RESET_STATUS, 'fake_group_001',
               fields.GroupStatus.AVAILABLE,
               exception.VersionNotFoundForAPIMethod),
              (mv.GROUP_VOLUME_RESET_STATUS, 'fake_group_001',
               fields.GroupStatus.AVAILABLE,
               exception.GroupNotFound),
              (mv.GROUP_VOLUME_RESET_STATUS, None,
               'invalid_test_status',
               exception.InvalidGroupStatus),
              (mv.GROUP_VOLUME_RESET_STATUS, 'fake_group_001',
               None,
               exception.ValidationError)
              )
    @ddt.unpack
    def test_reset_group_status_illegal(self, version, group_id,
                                        status, exceptions):
        g_id = group_id or self.group2.id
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, g_id),
                                      version=version)
        body = {"reset_status": {
            "status": status
        }}
        self.assertRaises(exceptions,
                          self.controller.reset_status,
                          req, g_id, body=body)

    def test_reset_group_without_status(self):
        g_id = self.group2.id
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, g_id),
                                      version=mv.GROUP_VOLUME_RESET_STATUS)
        body = {"reset_status": {}}
        self.assertRaises(exception.ValidationError,
                          self.controller.reset_status,
                          req, g_id, body=body)

    def test_reset_group_status(self):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group2.id),
                                      version=mv.GROUP_VOLUME_RESET_STATUS)
        req.environ['cinder.context'] = self.ctxt
        body = {"reset_status": {
            "status": fields.GroupStatus.AVAILABLE
        }}
        response = self.controller.reset_status(req,
                                                self.group2.id, body=body)

        group = objects.Group.get_by_id(self.ctxt, self.group2.id)
        self.assertEqual(HTTPStatus.ACCEPTED, response.status_int)
        self.assertEqual(fields.GroupStatus.AVAILABLE, group.status)

    @ddt.data(True, False)
    @mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.validate_host_capacity')
    def test_create_group_from_src_snap(self, valid_host, mock_validate_host):
        self.mock_object(volume_api.API, "create", v3_fakes.fake_volume_create)

        group = utils.create_group(self.ctxt,
                                   group_type_id=fake.GROUP_TYPE_ID,
                                   volume_type_ids=[fake.VOLUME_TYPE_ID])
        volume = utils.create_volume(
            self.ctxt,
            group_id=group.id,
            volume_type_id=fake.VOLUME_TYPE_ID)
        group_snapshot = utils.create_group_snapshot(
            self.ctxt, group_id=group.id,
            group_type_id=group.group_type_id)
        snapshot = utils.create_snapshot(
            self.ctxt,
            volume.id,
            group_snapshot_id=group_snapshot.id,
            status=fields.SnapshotStatus.AVAILABLE,
            volume_type_id=volume.volume_type_id)
        mock_validate_host.return_value = valid_host

        test_grp_name = 'test grp'
        body = {"create-from-src": {"name": test_grp_name,
                                    "description": "Group 1",
                                    "group_snapshot_id": group_snapshot.id}}
        req = fakes.HTTPRequest.blank('/v3/%s/groups/action' %
                                      fake.PROJECT_ID,
                                      version=mv.GROUP_SNAPSHOTS)
        if valid_host:
            res_dict = self.controller.create_from_src(req, body=body)

            self.assertIn('id', res_dict['group'])
            self.assertEqual(test_grp_name, res_dict['group']['name'])
            grp_ref = objects.Group.get_by_id(
                self.ctxt.elevated(), res_dict['group']['id'])
        else:
            self.assertRaises(webob.exc.HTTPBadRequest,
                              self.controller.create_from_src, req, body=body)
            groups = objects.GroupList.get_all_by_project(self.ctxt,
                                                          fake.PROJECT_ID)
            grp_ref = objects.Group.get_by_id(
                self.ctxt.elevated(), groups[0]['id'])
        grp_ref.destroy()
        snapshot.destroy()
        volume.destroy()
        group.destroy()
        group_snapshot.destroy()

    @ddt.data(True, False)
    @mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.validate_host_capacity')
    def test_create_group_from_src_grp(self, host_valid, mock_validate_host):
        self.mock_object(volume_api.API, "create", v3_fakes.fake_volume_create)

        source_grp = utils.create_group(self.ctxt,
                                        group_type_id=fake.GROUP_TYPE_ID,
                                        volume_type_ids=[fake.VOLUME_TYPE_ID])
        volume = utils.create_volume(
            self.ctxt,
            group_id=source_grp.id,
            volume_type_id=fake.VOLUME_TYPE_ID)
        mock_validate_host.return_value = host_valid

        test_grp_name = 'test cg'
        body = {"create-from-src": {"name": test_grp_name,
                                    "description": "Consistency Group 1",
                                    "source_group_id": source_grp.id}}
        req = fakes.HTTPRequest.blank('/v3/%s/groups/action' %
                                      fake.PROJECT_ID,
                                      version=mv.GROUP_SNAPSHOTS)
        if host_valid:
            res_dict = self.controller.create_from_src(req, body=body)
            self.assertIn('id', res_dict['group'])
            self.assertEqual(test_grp_name, res_dict['group']['name'])
            grp = objects.Group.get_by_id(
                self.ctxt, res_dict['group']['id'])
            grp.destroy()
            volume.destroy()
            source_grp.destroy()
        else:
            self.assertRaises(webob.exc.HTTPBadRequest,
                              self.controller.create_from_src, req, body=body)
            groups = objects.GroupList.get_all_by_project(self.ctxt,
                                                          fake.PROJECT_ID)
            grp = objects.Group.get_by_id(
                self.ctxt.elevated(), groups[0]['id'])
        grp.destroy()
        volume.destroy()
        source_grp.destroy()

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=True)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    def test_enable_replication(self, mock_rep_grp_type, mock_rep_vol_type):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group3.id),
                                      version=mv.GROUP_REPLICATION)
        self.group3.status = fields.GroupStatus.AVAILABLE
        self.group3.save()
        body = {"enable_replication": {}}
        response = self.controller.enable_replication(req,
                                                      self.group3.id,
                                                      body=body)

        group = objects.Group.get_by_id(self.ctxt, self.group3.id)
        self.assertEqual(202, response.status_int)
        self.assertEqual(fields.GroupStatus.AVAILABLE, group.status)
        self.assertEqual(fields.ReplicationStatus.ENABLING,
                         group.replication_status)

    @ddt.data((True, False), (False, True), (False, False))
    @ddt.unpack
    @mock.patch('cinder.volume.volume_utils.is_replicated_spec')
    @mock.patch('cinder.volume.volume_utils.is_group_a_type')
    def test_enable_replication_wrong_type(self, is_grp_rep_type,
                                           is_vol_rep_type,
                                           mock_rep_grp_type,
                                           mock_rep_vol_type):
        mock_rep_grp_type.return_value = is_grp_rep_type
        mock_rep_vol_type.return_value = is_vol_rep_type
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group3.id),
                                      version=mv.GROUP_REPLICATION)
        self.group3.status = fields.GroupStatus.AVAILABLE
        self.group3.save()
        body = {"enable_replication": {}}
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.enable_replication,
                          req, self.group3.id, body=body)

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=False)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    def test_enable_replication_wrong_group_type(self, mock_rep_grp_type,
                                                 mock_rep_vol_type):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group3.id),
                                      version=mv.GROUP_REPLICATION)
        self.group3.status = fields.GroupStatus.AVAILABLE
        self.group3.save()
        body = {"enable_replication": {}}
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.enable_replication,
                          req, self.group3.id, body=body)

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=True)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    @ddt.data((mv.GROUP_REPLICATION, True,
               fields.GroupStatus.CREATING,
               webob.exc.HTTPBadRequest),
              (mv.GROUP_REPLICATION, False,
               fields.GroupStatus.AVAILABLE,
               exception.GroupNotFound),
              (INVALID_GROUP_REPLICATION, True,
               fields.GroupStatus.AVAILABLE,
               exception.VersionNotFoundForAPIMethod),
              )
    @ddt.unpack
    def test_enable_replication_negative(self, version, not_fake,
                                         status, exceptions,
                                         mock_rep_grp_type, mock_rep_vol_type):
        if not_fake:
            group_id = self.group3.id
        else:
            group_id = fake.GROUP_ID
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, group_id),
                                      version=version)
        if not_fake:
            self.group3.status = status
            self.group3.save()
        body = {"enable_replication": {}}
        self.assertRaises(exceptions,
                          self.controller.enable_replication,
                          req, group_id, body=body)

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=True)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    def test_disable_replication(self, mock_rep_grp_type, mock_rep_vol_type):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group3.id),
                                      version=mv.GROUP_REPLICATION)
        self.group3.status = fields.GroupStatus.AVAILABLE
        self.group3.replication_status = fields.ReplicationStatus.ENABLED
        self.group3.save()
        body = {"disable_replication": {}}
        response = self.controller.disable_replication(req,
                                                       self.group3.id,
                                                       body=body)

        group = objects.Group.get_by_id(self.ctxt, self.group3.id)
        self.assertEqual(202, response.status_int)
        self.assertEqual(fields.GroupStatus.AVAILABLE, group.status)
        self.assertEqual(fields.ReplicationStatus.DISABLING,
                         group.replication_status)

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=True)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    @ddt.data((mv.GROUP_REPLICATION, True,
               fields.GroupStatus.CREATING,
               fields.ReplicationStatus.ENABLED,
               webob.exc.HTTPBadRequest),
              (mv.GROUP_REPLICATION, True,
               fields.GroupStatus.AVAILABLE,
               fields.ReplicationStatus.DISABLED,
               webob.exc.HTTPBadRequest),
              (mv.GROUP_REPLICATION, False,
               fields.GroupStatus.AVAILABLE,
               fields.ReplicationStatus.DISABLED,
               exception.GroupNotFound),
              (INVALID_GROUP_REPLICATION, True,
               fields.GroupStatus.AVAILABLE,
               fields.ReplicationStatus.ENABLED,
               exception.VersionNotFoundForAPIMethod),
              )
    @ddt.unpack
    def test_disable_replication_negative(self, version, not_fake,
                                          status, rep_status, exceptions,
                                          mock_rep_grp_type,
                                          mock_rep_vol_type):
        if not_fake:
            group_id = self.group3.id
        else:
            group_id = fake.GROUP_ID
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, group_id),
                                      version=version)
        if not_fake:
            self.group3.status = status
            self.group3.replication_status = rep_status
            self.group3.save()
        body = {"disable_replication": {}}
        self.assertRaises(exceptions,
                          self.controller.disable_replication,
                          req, group_id, body=body)

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=True)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    def test_failover_replication(self, mock_rep_grp_type, mock_rep_vol_type):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group3.id),
                                      version=mv.GROUP_REPLICATION)
        self.group3.status = fields.GroupStatus.AVAILABLE
        self.group3.replication_status = fields.ReplicationStatus.ENABLED
        self.group3.save()
        body = {"failover_replication": {}}
        response = self.controller.failover_replication(req,
                                                        self.group3.id,
                                                        body=body)

        group = objects.Group.get_by_id(self.ctxt, self.group3.id)
        self.assertEqual(202, response.status_int)
        self.assertEqual(fields.GroupStatus.AVAILABLE, group.status)
        self.assertEqual(fields.ReplicationStatus.FAILING_OVER,
                         group.replication_status)

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=True)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    @ddt.data((mv.GROUP_REPLICATION, True,
               fields.GroupStatus.CREATING,
               fields.ReplicationStatus.ENABLED,
               webob.exc.HTTPBadRequest),
              (mv.GROUP_REPLICATION, True,
               fields.GroupStatus.AVAILABLE,
               fields.ReplicationStatus.DISABLED,
               webob.exc.HTTPBadRequest),
              (mv.GROUP_REPLICATION, False,
               fields.GroupStatus.AVAILABLE,
               fields.ReplicationStatus.DISABLED,
               exception.GroupNotFound),
              (INVALID_GROUP_REPLICATION, True,
               fields.GroupStatus.AVAILABLE,
               fields.ReplicationStatus.ENABLED,
               exception.VersionNotFoundForAPIMethod),
              )
    @ddt.unpack
    def test_failover_replication_negative(self, version, not_fake,
                                           status, rep_status, exceptions,
                                           mock_rep_grp_type,
                                           mock_rep_vol_type):
        if not_fake:
            group_id = self.group3.id
        else:
            group_id = fake.GROUP_ID
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, group_id),
                                      version=version)
        if not_fake:
            self.group3.status = status
            self.group3.replication_status = rep_status
            self.group3.save()
        body = {"failover_replication": {}}
        self.assertRaises(exceptions,
                          self.controller.failover_replication,
                          req, group_id, body=body)

    @mock.patch('cinder.volume.volume_utils.is_replicated_spec',
                return_value=True)
    @mock.patch('cinder.volume.volume_utils.is_group_a_type',
                return_value=True)
    @mock.patch('cinder.volume.rpcapi.VolumeAPI.list_replication_targets')
    def test_list_replication_targets(self, mock_list_rep_targets,
                                      mock_rep_grp_type, mock_rep_vol_type):
        req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
                                      (fake.PROJECT_ID, self.group3.id),
                                      version=mv.GROUP_REPLICATION)
        targets = {
            'replication_targets': [
                {'backend_id': 'lvm_backend_1'}
            ]
        }
        mock_list_rep_targets.return_value = targets
        self.group3.status = fields.GroupStatus.AVAILABLE
        self.group3.save()
        body = {"list_replication_targets": {}}
        response = self.controller.list_replication_targets(
            req, self.group3.id, body=body)

        self.assertIn('replication_targets', response)
        self.assertEqual('lvm_backend_1',
                         response['replication_targets'][0]['backend_id'])

    def test_show_group_with_project_id(self):
        # If the microversion >= 3.58 and "is_admin=True", "project_id" should
        # be contained in the response body.
        req = fakes.HTTPRequest.blank(
            '/v3/%s/groups/%s' % (fake.PROJECT_ID, self.group1.id),
            version=mv.GROUP_GROUPSNAPSHOT_PROJECT_ID,
            use_admin_context=True)
        res_dict = self.controller.show(req, self.group1.id)
        self.assertEqual(1, len(res_dict))
        self.assertEqual(fake.PROJECT_ID,
                         res_dict['group']['project_id'])

        # If the microversion < 3.58, "project_id" should not be
        # contained in the response body.
        req = fakes.HTTPRequest.blank(
            '/v3/%s/groups/%s' %
            (fake.PROJECT_ID, self.group1.id),
            version=mv.get_prior_version(mv.GROUP_GROUPSNAPSHOT_PROJECT_ID),
            use_admin_context=True)
        res_dict = self.controller.show(req, self.group1.id)
        self.assertEqual(1, len(res_dict))
        self.assertNotIn('project_id', res_dict['group'])

    def test_list_groups_with_project_id(self):
        self.group1.group_type_id = fake.GROUP_TYPE_ID
        self.group1.save()

        self.group2.group_type_id = fake.GROUP_TYPE2_ID
        self.group2.save()

        req = fakes.HTTPRequest.blank(
            '/v3/%s/groups/detail' % self.ctxt.project_id,
            version=mv.GROUP_GROUPSNAPSHOT_PROJECT_ID,
            use_admin_context=True)
        res_dict = self.controller.detail(req)

        self.assertEqual(1, len(res_dict))
        self.assertEqual(self.group1.project_id,
                         res_dict['groups'][0]['project_id'])
        self.assertEqual(self.group2.project_id,
                         res_dict['groups'][1]['project_id'])

    def test_show_group_without_project_id(self):
        # If the microversion >= 3.58 and "is_admin=False", "project_id" should
        # not be contained in the response body.
        req = fakes.HTTPRequest.blank(
            '/v3/%s/groups/%s' % (fake.PROJECT_ID, self.group3.id),
            version=mv.GROUP_GROUPSNAPSHOT_PROJECT_ID)
        res_dict = self.controller.show(req, self.group1.id)
        self.assertEqual(1, len(res_dict))
        self.assertNotIn('project_id', res_dict['group'])
