"Fossies" - the Fresh Open Source Software Archive

Member "cinder-13.0.7/cinder/tests/unit/api/v3/test_volumes.py" (4 Oct 2019, 42327 Bytes) of package /linux/misc/openstack/cinder-13.0.7.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "test_volumes.py": 14.0.2_vs_15.0.0.

    1 #
    2 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    3 #    not use this file except in compliance with the License. You may obtain
    4 #    a copy of the License at
    5 #
    6 #         http://www.apache.org/licenses/LICENSE-2.0
    7 #
    8 #    Unless required by applicable law or agreed to in writing, software
    9 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   10 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   11 #    License for the specific language governing permissions and limitations
   12 #    under the License.
   13 
   14 import datetime
   15 import ddt
   16 import iso8601
   17 
   18 import mock
   19 from oslo_serialization import jsonutils
   20 from oslo_utils import strutils
   21 from six.moves import http_client
   22 import webob
   23 
   24 from cinder.api import extensions
   25 from cinder.api import microversions as mv
   26 from cinder.api.v2.views.volumes import ViewBuilder
   27 from cinder.api.v3 import volumes
   28 from cinder.backup import api as backup_api
   29 from cinder import context
   30 from cinder import db
   31 from cinder import exception
   32 from cinder.group import api as group_api
   33 from cinder import objects
   34 from cinder.objects import fields
   35 from cinder.policies import volumes as policy
   36 from cinder import test
   37 from cinder.tests.unit.api import fakes
   38 from cinder.tests.unit.api.v2 import fakes as v2_fakes
   39 from cinder.tests.unit.api.v2 import test_volumes as v2_test_volumes
   40 from cinder.tests.unit import fake_constants as fake
   41 from cinder.tests.unit.image import fake as fake_image
   42 from cinder.tests.unit import utils as test_utils
   43 from cinder import utils
   44 from cinder.volume import api as volume_api
   45 from cinder.volume import api as vol_get
   46 
   47 DEFAULT_AZ = "zone1:host1"
   48 
   49 
   50 @ddt.ddt
   51 class VolumeApiTest(test.TestCase):
   52     def setUp(self):
   53         super(VolumeApiTest, self).setUp()
   54         self.ext_mgr = extensions.ExtensionManager()
   55         self.ext_mgr.extensions = {}
   56         fake_image.mock_image_service(self)
   57         self.controller = volumes.VolumeController(self.ext_mgr)
   58 
   59         self.flags(host='fake')
   60         self.ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
   61 
   62     def test_check_volume_filters_called(self):
   63         with mock.patch.object(vol_get.API,
   64                                'check_volume_filters') as volume_get:
   65             req = fakes.HTTPRequest.blank('/v3/volumes?bootable=True')
   66             req.method = 'GET'
   67             req.content_type = 'application/json'
   68             req.headers = mv.get_mv_header(mv.BASE_VERSION)
   69             req.environ['cinder.context'].is_admin = True
   70 
   71             self.override_config('query_volume_filters', 'bootable')
   72             self.controller.index(req)
   73             filters = req.params.copy()
   74 
   75             volume_get.assert_called_with(filters, False)
   76 
   77     def test_check_volume_filters_strict_called(self):
   78 
   79         with mock.patch.object(vol_get.API,
   80                                'check_volume_filters') as volume_get:
   81             req = fakes.HTTPRequest.blank('/v3/volumes?bootable=True')
   82             req.method = 'GET'
   83             req.content_type = 'application/json'
   84             req.headers = mv.get_mv_header(mv.VOLUME_LIST_BOOTABLE)
   85             req.environ['cinder.context'].is_admin = True
   86             req.api_version_request = mv.get_api_version(
   87                 mv.VOLUME_LIST_BOOTABLE)
   88 
   89             self.override_config('query_volume_filters', 'bootable')
   90             self.controller.index(req)
   91             filters = req.params.copy()
   92 
   93             volume_get.assert_called_with(filters, True)
   94 
   95     def _create_volume_with_glance_metadata(self):
   96         vol1 = db.volume_create(self.ctxt, {'display_name': 'test1',
   97                                             'project_id':
   98                                             self.ctxt.project_id})
   99         db.volume_glance_metadata_create(self.ctxt, vol1.id, 'image_name',
  100                                          'imageTestOne')
  101         vol2 = db.volume_create(self.ctxt, {'display_name': 'test2',
  102                                             'project_id':
  103                                             self.ctxt.project_id})
  104         db.volume_glance_metadata_create(self.ctxt, vol2.id, 'image_name',
  105                                          'imageTestTwo')
  106         db.volume_glance_metadata_create(self.ctxt, vol2.id, 'disk_format',
  107                                          'qcow2')
  108         return [vol1, vol2]
  109 
  110     def _create_volume_with_group(self):
  111         vol1 = db.volume_create(self.ctxt, {'display_name': 'test1',
  112                                             'project_id':
  113                                             self.ctxt.project_id,
  114                                             'group_id':
  115                                             fake.GROUP_ID})
  116         vol2 = db.volume_create(self.ctxt, {'display_name': 'test2',
  117                                             'project_id':
  118                                             self.ctxt.project_id,
  119                                             'group_id':
  120                                             fake.GROUP2_ID})
  121         return [vol1, vol2]
  122 
  123     def _create_multiple_volumes_with_different_project(self):
  124         # Create volumes in project 1
  125         db.volume_create(self.ctxt, {'display_name': 'test1',
  126                                      'project_id': fake.PROJECT_ID})
  127         db.volume_create(self.ctxt, {'display_name': 'test2',
  128                                      'project_id': fake.PROJECT_ID})
  129         # Create volume in project 2
  130         db.volume_create(self.ctxt, {'display_name': 'test3',
  131                                      'project_id': fake.PROJECT2_ID})
  132 
  133     def test_volume_index_filter_by_glance_metadata(self):
  134         vols = self._create_volume_with_glance_metadata()
  135         req = fakes.HTTPRequest.blank("/v3/volumes?glance_metadata="
  136                                       "{'image_name': 'imageTestOne'}")
  137         req.headers = mv.get_mv_header(mv.VOLUME_LIST_GLANCE_METADATA)
  138         req.api_version_request = mv.get_api_version(
  139             mv.VOLUME_LIST_GLANCE_METADATA)
  140         req.environ['cinder.context'] = self.ctxt
  141         res_dict = self.controller.index(req)
  142         volumes = res_dict['volumes']
  143         self.assertEqual(1, len(volumes))
  144         self.assertEqual(vols[0].id, volumes[0]['id'])
  145 
  146     def test_volume_index_filter_by_glance_metadata_in_unsupport_version(self):
  147         self._create_volume_with_glance_metadata()
  148         req = fakes.HTTPRequest.blank("/v3/volumes?glance_metadata="
  149                                       "{'image_name': 'imageTestOne'}")
  150         req.headers = mv.get_mv_header(mv.BASE_VERSION)
  151         req.api_version_request = mv.get_api_version(mv.BASE_VERSION)
  152         req.environ['cinder.context'] = self.ctxt
  153         res_dict = self.controller.index(req)
  154         volumes = res_dict['volumes']
  155         self.assertEqual(2, len(volumes))
  156 
  157     def test_volume_index_filter_by_group_id(self):
  158         vols = self._create_volume_with_group()
  159         req = fakes.HTTPRequest.blank(("/v3/volumes?group_id=%s") %
  160                                       fake.GROUP_ID)
  161         req.headers = mv.get_mv_header(mv.VOLUME_LIST_GROUP)
  162         req.api_version_request = mv.get_api_version(mv.VOLUME_LIST_GROUP)
  163         req.environ['cinder.context'] = self.ctxt
  164         res_dict = self.controller.index(req)
  165         volumes = res_dict['volumes']
  166         self.assertEqual(1, len(volumes))
  167         self.assertEqual(vols[0].id, volumes[0]['id'])
  168 
  169     @ddt.data('volumes', 'volumes/detail')
  170     def test_list_volume_with_count_param_version_not_matched(self, action):
  171         self._create_multiple_volumes_with_different_project()
  172 
  173         is_detail = True if 'detail' in action else False
  174         req = fakes.HTTPRequest.blank("/v3/%s?with_count=True" % action)
  175         req.headers = mv.get_mv_header(
  176             mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
  177         req.api_version_request = mv.get_api_version(
  178             mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
  179         ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
  180         req.environ['cinder.context'] = ctxt
  181         res_dict = self.controller._get_volumes(req, is_detail=is_detail)
  182         self.assertNotIn('count', res_dict)
  183 
  184     @ddt.data({'method': 'volumes',
  185                'display_param': 'True'},
  186               {'method': 'volumes',
  187                'display_param': 'False'},
  188               {'method': 'volumes',
  189                'display_param': '1'},
  190               {'method': 'volumes/detail',
  191                'display_param': 'True'},
  192               {'method': 'volumes/detail',
  193                'display_param': 'False'},
  194               {'method': 'volumes/detail',
  195                'display_param': '1'}
  196               )
  197     @ddt.unpack
  198     def test_list_volume_with_count_param(self, method, display_param):
  199         self._create_multiple_volumes_with_different_project()
  200 
  201         is_detail = True if 'detail' in method else False
  202         show_count = strutils.bool_from_string(display_param, strict=True)
  203         # Request with 'with_count' and 'limit'
  204         req = fakes.HTTPRequest.blank(
  205             "/v3/%s?with_count=%s&limit=1" % (method, display_param))
  206         req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
  207         req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
  208         ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
  209         req.environ['cinder.context'] = ctxt
  210         res_dict = self.controller._get_volumes(req, is_detail=is_detail)
  211         self.assertEqual(1, len(res_dict['volumes']))
  212         if show_count:
  213             self.assertEqual(2, res_dict['count'])
  214         else:
  215             self.assertNotIn('count', res_dict)
  216 
  217         # Request with 'with_count'
  218         req = fakes.HTTPRequest.blank(
  219             "/v3/%s?with_count=%s" % (method, display_param))
  220         req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
  221         req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
  222         ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
  223         req.environ['cinder.context'] = ctxt
  224         res_dict = self.controller._get_volumes(req, is_detail=is_detail)
  225         self.assertEqual(2, len(res_dict['volumes']))
  226         if show_count:
  227             self.assertEqual(2, res_dict['count'])
  228         else:
  229             self.assertNotIn('count', res_dict)
  230 
  231         # Request with admin context and 'all_tenants'
  232         req = fakes.HTTPRequest.blank(
  233             "/v3/%s?with_count=%s&all_tenants=1" % (method, display_param))
  234         req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
  235         req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
  236         ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
  237         req.environ['cinder.context'] = ctxt
  238         res_dict = self.controller._get_volumes(req, is_detail=is_detail)
  239         self.assertEqual(3, len(res_dict['volumes']))
  240         if show_count:
  241             self.assertEqual(3, res_dict['count'])
  242         else:
  243             self.assertNotIn('count', res_dict)
  244 
  245     def test_volume_index_filter_by_group_id_in_unsupport_version(self):
  246         self._create_volume_with_group()
  247         req = fakes.HTTPRequest.blank(("/v3/volumes?group_id=%s") %
  248                                       fake.GROUP_ID)
  249         req.headers = mv.get_mv_header(mv.BACKUP_UPDATE)
  250         req.api_version_request = mv.get_api_version(mv.BACKUP_UPDATE)
  251         req.environ['cinder.context'] = self.ctxt
  252         res_dict = self.controller.index(req)
  253         volumes = res_dict['volumes']
  254         self.assertEqual(2, len(volumes))
  255 
  256     def _fake_volumes_summary_request(self,
  257                                       version=mv.VOLUME_SUMMARY,
  258                                       all_tenant=False,
  259                                       is_admin=False):
  260         req_url = '/v3/volumes/summary'
  261         if all_tenant:
  262             req_url += '?all_tenants=True'
  263         req = fakes.HTTPRequest.blank(req_url, use_admin_context=is_admin)
  264         req.headers = mv.get_mv_header(version)
  265         req.api_version_request = mv.get_api_version(version)
  266         return req
  267 
  268     @mock.patch.object(db.sqlalchemy.api, '_volume_type_get_full',
  269                        autospec=True)
  270     @mock.patch.object(volume_api.API, 'get_snapshot', autospec=True)
  271     @mock.patch.object(volume_api.API, 'create', autospec=True)
  272     @mock.patch(
  273         'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
  274     def test_volume_create_with_snapshot_image(self, mock_validate, create,
  275                                                get_snapshot, volume_type_get):
  276         create.side_effect = v2_fakes.fake_volume_api_create
  277         get_snapshot.side_effect = v2_fakes.fake_snapshot_get
  278         volume_type_get.side_effect = v2_fakes.fake_volume_type_get
  279 
  280         self.ext_mgr.extensions = {'os-image-create': 'fake'}
  281         vol = self._vol_in_request_body(
  282             image_id="b0a599e0-41d7-3582-b260-769f443c862a")
  283 
  284         snapshot_id = fake.SNAPSHOT_ID
  285         ex = self._expected_vol_from_controller(snapshot_id=snapshot_id)
  286         body = {"volume": vol}
  287         req = fakes.HTTPRequest.blank('/v3/volumes')
  288         req.headers = mv.get_mv_header(mv.SUPPORT_NOVA_IMAGE)
  289         req.api_version_request = mv.get_api_version(mv.SUPPORT_NOVA_IMAGE)
  290         res_dict = self.controller.create(req, body=body)
  291         self.assertEqual(ex, res_dict)
  292         context = req.environ['cinder.context']
  293         get_snapshot.assert_called_once_with(self.controller.volume_api,
  294                                              context, snapshot_id)
  295         kwargs = self._expected_volume_api_create_kwargs(
  296             v2_fakes.fake_snapshot(snapshot_id))
  297         create.assert_called_once_with(
  298             self.controller.volume_api, context,
  299             vol['size'], v2_fakes.DEFAULT_VOL_NAME,
  300             v2_fakes.DEFAULT_VOL_DESCRIPTION, **kwargs)
  301 
  302     def test_volumes_summary_in_unsupport_version(self):
  303         """Function call to test summary volumes API in unsupported version"""
  304         req = self._fake_volumes_summary_request(
  305             version=mv.get_prior_version(mv.VOLUME_SUMMARY))
  306         self.assertRaises(exception.VersionNotFoundForAPIMethod,
  307                           self.controller.summary, req)
  308 
  309     def test_volumes_summary_in_supported_version(self):
  310         """Function call to test the summary volumes API for version v3."""
  311         req = self._fake_volumes_summary_request()
  312         res_dict = self.controller.summary(req)
  313         expected = {'volume-summary': {'total_size': 0.0, 'total_count': 0}}
  314         self.assertEqual(expected, res_dict)
  315 
  316         vol = v2_test_volumes.VolumeApiTest._vol_in_request_body(
  317             availability_zone="nova")
  318         body = {"volume": vol}
  319         req = fakes.HTTPRequest.blank('/v3/volumes')
  320         res_dict = self.controller.create(req, body=body)
  321 
  322         req = self._fake_volumes_summary_request()
  323         res_dict = self.controller.summary(req)
  324         expected = {'volume-summary': {'total_size': 1.0, 'total_count': 1}}
  325         self.assertEqual(expected, res_dict)
  326 
  327     @ddt.data(
  328         (mv.get_prior_version(mv.VOLUME_SUMMARY_METADATA),
  329          {'volume-summary': {'total_size': 0.0,
  330                              'total_count': 0}}),
  331         (mv.VOLUME_SUMMARY_METADATA, {'volume-summary': {'total_size': 0.0,
  332                                                          'total_count': 0,
  333                                                          'metadata': {}}}))
  334     @ddt.unpack
  335     def test_volume_summary_empty(self, summary_api_version, expect_result):
  336         req = self._fake_volumes_summary_request(version=summary_api_version)
  337         res_dict = self.controller.summary(req)
  338         self.assertEqual(expect_result, res_dict)
  339 
  340     @ddt.data(
  341         (mv.get_prior_version(mv.VOLUME_SUMMARY_METADATA),
  342          {'volume-summary': {'total_size': 2,
  343                              'total_count': 2}}),
  344         (mv.VOLUME_SUMMARY_METADATA,
  345          {'volume-summary': {'total_size': 2,
  346                              'total_count': 2,
  347                              'metadata': {
  348                                  'name': ['test_name1', 'test_name2'],
  349                                  'age': ['test_age']}}}))
  350     @ddt.unpack
  351     def test_volume_summary_return_metadata(self, summary_api_version,
  352                                             expect_result):
  353         test_utils.create_volume(self.ctxt, metadata={'name': 'test_name1',
  354                                                       'age': 'test_age'})
  355         test_utils.create_volume(self.ctxt, metadata={'name': 'test_name2',
  356                                                       'age': 'test_age'})
  357         ctxt2 = context.RequestContext(fake.USER_ID, fake.PROJECT2_ID, True)
  358         test_utils.create_volume(ctxt2, metadata={'name': 'test_name3'})
  359 
  360         req = self._fake_volumes_summary_request(version=summary_api_version)
  361         res_dict = self.controller.summary(req)
  362         self.assertEqual(expect_result, res_dict)
  363 
  364     @ddt.data(
  365         (mv.get_prior_version(mv.VOLUME_SUMMARY_METADATA),
  366             {'volume-summary': {'total_size': 2,
  367                                 'total_count': 2}}),
  368         (mv.VOLUME_SUMMARY_METADATA,
  369             {'volume-summary': {'total_size': 2,
  370                                 'total_count': 2,
  371                                 'metadata': {
  372                                     'name': ['test_name1', 'test_name2'],
  373                                     'age': ['test_age']}}}))
  374     @ddt.unpack
  375     def test_volume_summary_return_metadata_all_tenant(
  376             self, summary_api_version, expect_result):
  377         test_utils.create_volume(self.ctxt, metadata={'name': 'test_name1',
  378                                                       'age': 'test_age'})
  379         ctxt2 = context.RequestContext(fake.USER_ID, fake.PROJECT2_ID, True)
  380         test_utils.create_volume(ctxt2, metadata={'name': 'test_name2',
  381                                                   'age': 'test_age'})
  382 
  383         req = self._fake_volumes_summary_request(version=summary_api_version,
  384                                                  all_tenant=True,
  385                                                  is_admin=True)
  386         res_dict = self.controller.summary(req)
  387         self.assertEqual(expect_result, res_dict)
  388 
  389     def _vol_in_request_body(self,
  390                              size=v2_fakes.DEFAULT_VOL_SIZE,
  391                              name=v2_fakes.DEFAULT_VOL_NAME,
  392                              description=v2_fakes.DEFAULT_VOL_DESCRIPTION,
  393                              availability_zone=DEFAULT_AZ,
  394                              snapshot_id=None,
  395                              source_volid=None,
  396                              consistencygroup_id=None,
  397                              volume_type=None,
  398                              image_ref=None,
  399                              image_id=None,
  400                              group_id=None,
  401                              backup_id=None):
  402         vol = {"size": size,
  403                "name": name,
  404                "description": description,
  405                "availability_zone": availability_zone,
  406                "snapshot_id": snapshot_id,
  407                "source_volid": source_volid,
  408                "consistencygroup_id": consistencygroup_id,
  409                "volume_type": volume_type,
  410                "group_id": group_id,
  411                }
  412 
  413         if image_id is not None:
  414             vol['image_id'] = image_id
  415         elif image_ref is not None:
  416             vol['imageRef'] = image_ref
  417         elif backup_id is not None:
  418             vol['backup_id'] = backup_id
  419 
  420         return vol
  421 
  422     def _expected_vol_from_controller(
  423             self,
  424             size=v2_fakes.DEFAULT_VOL_SIZE,
  425             availability_zone=DEFAULT_AZ,
  426             description=v2_fakes.DEFAULT_VOL_DESCRIPTION,
  427             name=v2_fakes.DEFAULT_VOL_NAME,
  428             consistencygroup_id=None,
  429             source_volid=None,
  430             snapshot_id=None,
  431             metadata=None,
  432             attachments=None,
  433             volume_type=v2_fakes.DEFAULT_VOL_TYPE,
  434             status=v2_fakes.DEFAULT_VOL_STATUS,
  435             with_migration_status=False,
  436             group_id=None,
  437             req_version=None):
  438         metadata = metadata or {}
  439         attachments = attachments or []
  440         volume = {'volume':
  441                   {'attachments': attachments,
  442                    'availability_zone': availability_zone,
  443                    'bootable': 'false',
  444                    'consistencygroup_id': consistencygroup_id,
  445                    'group_id': group_id,
  446                    'created_at': datetime.datetime(
  447                        1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC),
  448                    'updated_at': datetime.datetime(
  449                        1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC),
  450                    'description': description,
  451                    'id': v2_fakes.DEFAULT_VOL_ID,
  452                    'links':
  453                    [{'href': 'http://localhost/v3/%s/volumes/%s' % (
  454                              fake.PROJECT_ID, fake.VOLUME_ID),
  455                      'rel': 'self'},
  456                     {'href': 'http://localhost/%s/volumes/%s' % (
  457                              fake.PROJECT_ID, fake.VOLUME_ID),
  458                      'rel': 'bookmark'}],
  459                    'metadata': metadata,
  460                    'name': name,
  461                    'replication_status': 'disabled',
  462                    'multiattach': False,
  463                    'size': size,
  464                    'snapshot_id': snapshot_id,
  465                    'source_volid': source_volid,
  466                    'status': status,
  467                    'user_id': fake.USER_ID,
  468                    'volume_type': volume_type,
  469                    'encrypted': False}}
  470 
  471         if with_migration_status:
  472             volume['volume']['migration_status'] = None
  473 
  474         # Remove group_id if max version is less than GROUP_VOLUME.
  475         if req_version and req_version.matches(
  476                 None, mv.get_prior_version(mv.GROUP_VOLUME)):
  477             volume['volume'].pop('group_id')
  478 
  479         return volume
  480 
  481     def _expected_volume_api_create_kwargs(self, snapshot=None,
  482                                            availability_zone=DEFAULT_AZ,
  483                                            source_volume=None,
  484                                            test_group=None,
  485                                            req_version=None):
  486         volume = {
  487             'metadata': None,
  488             'snapshot': snapshot,
  489             'source_volume': source_volume,
  490             'consistencygroup': None,
  491             'availability_zone': availability_zone,
  492             'scheduler_hints': None,
  493             'multiattach': False,
  494             'group': test_group,
  495         }
  496 
  497         # Remove group_id if max version is less than GROUP_VOLUME.
  498         if req_version and req_version.matches(
  499                 None, mv.get_prior_version(mv.GROUP_VOLUME)):
  500             volume.pop('group')
  501 
  502         return volume
  503 
  504     @ddt.data((mv.GROUP_VOLUME,
  505                {'display_name': ' test name ',
  506                 'display_description': ' test desc ',
  507                 'size': 1}),
  508               (mv.get_prior_version(mv.GROUP_VOLUME),
  509                {'name': ' test name ',
  510                 'description': ' test desc ',
  511                 'size': 1}))
  512     @ddt.unpack
  513     def test_volume_create(self, max_ver, volume_body):
  514         self.mock_object(volume_api.API, 'get', v2_fakes.fake_volume_get)
  515         self.mock_object(volume_api.API, "create",
  516                          v2_fakes.fake_volume_api_create)
  517         self.mock_object(db.sqlalchemy.api, '_volume_type_get_full',
  518                          v2_fakes.fake_volume_type_get)
  519 
  520         req = fakes.HTTPRequest.blank('/v3/volumes')
  521         req.api_version_request = mv.get_api_version(max_ver)
  522 
  523         body = {'volume': volume_body}
  524         res_dict = self.controller.create(req, body=body)
  525         ex = self._expected_vol_from_controller(
  526             req_version=req.api_version_request, name='test name',
  527             description='test desc')
  528         self.assertEqual(ex['volume']['name'],
  529                          res_dict['volume']['name'])
  530         self.assertEqual(ex['volume']['description'],
  531                          res_dict['volume']['description'])
  532 
  533     @ddt.data(mv.get_prior_version(mv.VOLUME_DELETE_FORCE),
  534               mv.VOLUME_DELETE_FORCE)
  535     @mock.patch('cinder.context.RequestContext.authorize')
  536     def test_volume_delete_with_force(self, request_version, mock_authorize):
  537         mock_delete = self.mock_object(volume_api.API, "delete")
  538         self.mock_object(volume_api.API, 'get', return_value="fake_volume")
  539 
  540         req = fakes.HTTPRequest.blank('/v3/volumes/fake_id?force=True')
  541         req.api_version_request = mv.get_api_version(request_version)
  542         self.controller.delete(req, 'fake_id')
  543         context = req.environ['cinder.context']
  544         if request_version == mv.VOLUME_DELETE_FORCE:
  545             mock_authorize.assert_called_with(policy.FORCE_DELETE_POLICY,
  546                                               target_obj="fake_volume")
  547             mock_delete.assert_called_with(context,
  548                                            "fake_volume",
  549                                            cascade=False,
  550                                            force=True)
  551         else:
  552             mock_authorize.assert_not_called()
  553             mock_delete.assert_called_with(context,
  554                                            "fake_volume",
  555                                            cascade=False,
  556                                            force=False)
  557 
  558     @ddt.data(mv.GROUP_SNAPSHOTS, mv.get_prior_version(mv.GROUP_SNAPSHOTS))
  559     @mock.patch.object(group_api.API, 'get')
  560     @mock.patch.object(db.sqlalchemy.api, '_volume_type_get_full',
  561                        autospec=True)
  562     @mock.patch.object(volume_api.API, 'get_snapshot', autospec=True)
  563     @mock.patch.object(volume_api.API, 'create', autospec=True)
  564     def test_volume_creation_from_snapshot(self, max_ver, create, get_snapshot,
  565                                            volume_type_get, group_get):
  566         create.side_effect = v2_fakes.fake_volume_api_create
  567         get_snapshot.side_effect = v2_fakes.fake_snapshot_get
  568         volume_type_get.side_effect = v2_fakes.fake_volume_type_get
  569         fake_group = {
  570             'id': fake.GROUP_ID,
  571             'group_type_id': fake.GROUP_TYPE_ID,
  572             'name': 'fake_group'
  573         }
  574         group_get.return_value = fake_group
  575 
  576         snapshot_id = fake.SNAPSHOT_ID
  577         vol = self._vol_in_request_body(snapshot_id=snapshot_id,
  578                                         group_id=fake.GROUP_ID)
  579         body = {"volume": vol}
  580         req = fakes.HTTPRequest.blank('/v3/volumes')
  581         req.api_version_request = mv.get_api_version(max_ver)
  582         res_dict = self.controller.create(req, body=body)
  583         ex = self._expected_vol_from_controller(
  584             snapshot_id=snapshot_id,
  585             req_version=req.api_version_request)
  586         self.assertEqual(ex, res_dict)
  587 
  588         context = req.environ['cinder.context']
  589         get_snapshot.assert_called_once_with(self.controller.volume_api,
  590                                              context, snapshot_id)
  591 
  592         kwargs = self._expected_volume_api_create_kwargs(
  593             v2_fakes.fake_snapshot(snapshot_id),
  594             test_group=fake_group,
  595             req_version=req.api_version_request)
  596         create.assert_called_once_with(self.controller.volume_api, context,
  597                                        vol['size'], v2_fakes.DEFAULT_VOL_NAME,
  598                                        v2_fakes.DEFAULT_VOL_DESCRIPTION,
  599                                        **kwargs)
  600 
  601     @ddt.data(mv.VOLUME_CREATE_FROM_BACKUP,
  602               mv.get_prior_version(mv.VOLUME_CREATE_FROM_BACKUP))
  603     @mock.patch.object(db.sqlalchemy.api, '_volume_type_get_full',
  604                        autospec=True)
  605     @mock.patch.object(backup_api.API, 'get', autospec=True)
  606     @mock.patch.object(volume_api.API, 'create', autospec=True)
  607     def test_volume_creation_from_backup(self, max_ver, create, get_backup,
  608                                          volume_type_get):
  609         create.side_effect = v2_fakes.fake_volume_api_create
  610         get_backup.side_effect = v2_fakes.fake_backup_get
  611         volume_type_get.side_effect = v2_fakes.fake_volume_type_get
  612 
  613         backup_id = fake.BACKUP_ID
  614         req = fakes.HTTPRequest.blank('/v3/volumes')
  615         req.api_version_request = mv.get_api_version(max_ver)
  616         if max_ver == mv.VOLUME_CREATE_FROM_BACKUP:
  617             vol = self._vol_in_request_body(backup_id=backup_id)
  618         else:
  619             vol = self._vol_in_request_body()
  620         body = {"volume": vol}
  621         res_dict = self.controller.create(req, body=body)
  622         ex = self._expected_vol_from_controller(
  623             req_version=req.api_version_request)
  624         self.assertEqual(ex, res_dict)
  625 
  626         context = req.environ['cinder.context']
  627         kwargs = self._expected_volume_api_create_kwargs(
  628             req_version=req.api_version_request)
  629         if max_ver >= mv.VOLUME_CREATE_FROM_BACKUP:
  630             get_backup.assert_called_once_with(self.controller.backup_api,
  631                                                context, backup_id)
  632             kwargs.update({'backup': v2_fakes.fake_backup_get(None, context,
  633                                                               backup_id)})
  634         create.assert_called_once_with(self.controller.volume_api, context,
  635                                        vol['size'],
  636                                        v2_fakes.DEFAULT_VOL_NAME,
  637                                        v2_fakes.DEFAULT_VOL_DESCRIPTION,
  638                                        **kwargs)
  639 
  640     def test_volume_creation_with_scheduler_hints(self):
  641         vol = self._vol_in_request_body(availability_zone=None)
  642         vol.pop('group_id')
  643         body = {"volume": vol,
  644                 "OS-SCH-HNT:scheduler_hints": {
  645                     'different_host': [fake.UUID1, fake.UUID2]}}
  646         req = webob.Request.blank('/v3/%s/volumes' % fake.PROJECT_ID)
  647         req.method = 'POST'
  648         req.headers['Content-Type'] = 'application/json'
  649         req.body = jsonutils.dump_as_bytes(body)
  650         res = req.get_response(fakes.wsgi_app(
  651             fake_auth_context=self.ctxt))
  652         res_dict = jsonutils.loads(res.body)
  653         self.assertEqual(http_client.ACCEPTED, res.status_int)
  654         self.assertIn('id', res_dict['volume'])
  655 
  656     @ddt.data('fake_host', '', 1234, '          ')
  657     def test_volume_creation_invalid_scheduler_hints(self, invalid_hints):
  658         vol = self._vol_in_request_body()
  659         vol.pop('group_id')
  660         body = {"volume": vol,
  661                 "OS-SCH-HNT:scheduler_hints": {
  662                     'different_host': invalid_hints}}
  663         req = fakes.HTTPRequest.blank('/v3/volumes')
  664         self.assertRaises(exception.ValidationError, self.controller.create,
  665                           req, body=body)
  666 
  667     @ddt.data({'size': 'a'},
  668               {'size': ''},
  669               {'size': 0},
  670               {'size': 2 ** 31})
  671     def test_volume_creation_fails_with_invalid_parameters(
  672             self, vol_body):
  673         body = {"volume": vol_body}
  674         req = fakes.HTTPRequest.blank('/v3/volumes')
  675         self.assertRaises(exception.ValidationError, self.controller.create,
  676                           req, body=body)
  677 
  678     def test_volume_creation_fails_with_additional_properties(self):
  679         body = {"volume": {"size": 1, "user_id": fake.USER_ID,
  680                            "project_id": fake.PROJECT_ID}}
  681         req = fakes.HTTPRequest.blank('/v3/volumes')
  682         req.api_version_request = mv.get_api_version(
  683             mv.SUPPORT_VOLUME_SCHEMA_CHANGES)
  684         self.assertRaises(exception.ValidationError, self.controller.create,
  685                           req, body=body)
  686 
  687     def test_volume_update_without_vol_data(self):
  688         body = {"volume": {}}
  689         req = fakes.HTTPRequest.blank('/v3/volumes/%s' % fake.VOLUME_ID)
  690         req.api_version_request = mv.get_api_version(
  691             mv.SUPPORT_VOLUME_SCHEMA_CHANGES)
  692         self.assertRaises(exception.ValidationError, self.controller.update,
  693                           req, fake.VOLUME_ID, body=body)
  694 
  695     @ddt.data({'s': 'ea895e29-8485-4930-bbb8-c5616a309c0e'},
  696               ['ea895e29-8485-4930-bbb8-c5616a309c0e'],
  697               42)
  698     def test_volume_creation_fails_with_invalid_snapshot_type(self, value):
  699         snapshot_id = value
  700         vol = self._vol_in_request_body(snapshot_id=snapshot_id)
  701         body = {"volume": vol}
  702         req = fakes.HTTPRequest.blank('/v3/volumes')
  703         # Raise 400 when snapshot has not uuid type.
  704         self.assertRaises(exception.ValidationError, self.controller.create,
  705                           req, body=body)
  706 
  707     @ddt.data({'source_volid': 1},
  708               {'source_volid': []},
  709               {'consistencygroup_id': 1},
  710               {'consistencygroup_id': []})
  711     def test_volume_creation_fails_with_invalid_uuids(self, updated_uuids):
  712         vol = self._vol_in_request_body()
  713         vol.update(updated_uuids)
  714         body = {"volume": vol}
  715         req = fakes.HTTPRequest.blank('/v2/volumes')
  716         # Raise 400 for resource requested with invalid uuids.
  717         self.assertRaises(exception.ValidationError, self.controller.create,
  718                           req, body=body)
  719 
  720     @ddt.data(mv.get_prior_version(mv.RESOURCE_FILTER), mv.RESOURCE_FILTER,
  721               mv.LIKE_FILTER)
  722     @mock.patch.object(volume_api.API, 'check_volume_filters', mock.Mock())
  723     @mock.patch.object(utils, 'add_visible_admin_metadata', mock.Mock())
  724     @mock.patch('cinder.api.common.reject_invalid_filters')
  725     def test_list_volume_with_general_filter(self, version, mock_update):
  726         req = fakes.HTTPRequest.blank('/v3/volumes', version=version)
  727         self.controller.index(req)
  728         if version >= mv.RESOURCE_FILTER:
  729             support_like = True if version == mv.LIKE_FILTER else False
  730             mock_update.assert_called_once_with(req.environ['cinder.context'],
  731                                                 mock.ANY, 'volume',
  732                                                 support_like)
  733 
  734     @ddt.data({'admin': True, 'version': mv.VOLUME_DETAIL_PROVIDER_ID},
  735               {'admin': False, 'version': mv.VOLUME_DETAIL_PROVIDER_ID},
  736               {'admin': True,
  737                'version': mv.get_prior_version(mv.VOLUME_DETAIL_PROVIDER_ID)},
  738               {'admin': False,
  739                'version': mv.get_prior_version(mv.VOLUME_DETAIL_PROVIDER_ID)})
  740     @ddt.unpack
  741     def test_volume_show_provider_id(self, admin, version):
  742         self.mock_object(volume_api.API, 'get', v2_fakes.fake_volume_api_get)
  743         self.mock_object(db.sqlalchemy.api, '_volume_type_get_full',
  744                          v2_fakes.fake_volume_type_get)
  745 
  746         req = fakes.HTTPRequest.blank('/v3/volumes/%s' % fake.VOLUME_ID,
  747                                       version=version)
  748         if admin:
  749             admin_ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
  750                                                True)
  751             req.environ['cinder.context'] = admin_ctx
  752         res_dict = self.controller.show(req, fake.VOLUME_ID)
  753         req_version = req.api_version_request
  754         # provider_id is in view if min version is greater than or equal to
  755         # VOLUME_DETAIL_PROVIDER_ID for admin.
  756         if req_version.matches(mv.VOLUME_DETAIL_PROVIDER_ID, None) and admin:
  757             self.assertIn('provider_id', res_dict['volume'])
  758         else:
  759             self.assertNotIn('provider_id', res_dict['volume'])
  760 
  761     def _fake_create_volume(self, size=1):
  762         vol = {
  763             'display_name': 'fake_volume1',
  764             'status': 'available',
  765             'size': size
  766         }
  767         volume = objects.Volume(context=self.ctxt, **vol)
  768         volume.create()
  769         return volume
  770 
  771     def _fake_create_snapshot(self, volume_id, volume_size=1):
  772         snap = {
  773             'display_name': 'fake_snapshot1',
  774             'status': 'available',
  775             'volume_id': volume_id,
  776             'volume_size': volume_size
  777         }
  778         snapshot = objects.Snapshot(context=self.ctxt, **snap)
  779         snapshot.create()
  780         return snapshot
  781 
  782     @mock.patch.object(objects.Volume, 'get_latest_snapshot')
  783     @mock.patch.object(volume_api.API, 'get_volume')
  784     def test_volume_revert_with_snapshot_not_found(self, mock_volume,
  785                                                    mock_latest):
  786         fake_volume = self._fake_create_volume()
  787         mock_volume.return_value = fake_volume
  788         mock_latest.side_effect = exception.VolumeSnapshotNotFound(volume_id=
  789                                                                    'fake_id')
  790         req = fakes.HTTPRequest.blank('/v3/volumes/fake_id/revert')
  791         req.headers = mv.get_mv_header(mv.VOLUME_REVERT)
  792         req.api_version_request = mv.get_api_version(
  793             mv.VOLUME_REVERT)
  794 
  795         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.revert,
  796                           req, 'fake_id', {'revert': {'snapshot_id':
  797                                                       'fake_snapshot_id'}})
  798 
  799     @mock.patch.object(objects.Volume, 'get_latest_snapshot')
  800     @mock.patch.object(volume_api.API, 'get_volume')
  801     def test_volume_revert_with_snapshot_not_match(self, mock_volume,
  802                                                    mock_latest):
  803         fake_volume = self._fake_create_volume()
  804         mock_volume.return_value = fake_volume
  805         fake_snapshot = self._fake_create_snapshot(fake.UUID1)
  806         mock_latest.return_value = fake_snapshot
  807         req = fakes.HTTPRequest.blank('/v3/volumes/fake_id/revert')
  808         req.headers = mv.get_mv_header(mv.VOLUME_REVERT)
  809         req.api_version_request = mv.get_api_version(
  810             mv.VOLUME_REVERT)
  811 
  812         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.revert,
  813                           req, 'fake_id', {'revert': {'snapshot_id':
  814                                                       'fake_snapshot_id'}})
  815 
  816     @mock.patch.object(objects.Volume, 'get_latest_snapshot')
  817     @mock.patch('cinder.objects.base.'
  818                 'CinderPersistentObject.update_single_status_where')
  819     @mock.patch.object(volume_api.API, 'get_volume')
  820     def test_volume_revert_update_status_failed(self,
  821                                                 mock_volume,
  822                                                 mock_update,
  823                                                 mock_latest):
  824         fake_volume = self._fake_create_volume()
  825         fake_snapshot = self._fake_create_snapshot(fake_volume['id'])
  826         mock_volume.return_value = fake_volume
  827         mock_latest.return_value = fake_snapshot
  828         req = fakes.HTTPRequest.blank('/v3/volumes/%s/revert'
  829                                       % fake_volume['id'])
  830         req.headers = mv.get_mv_header(mv.VOLUME_REVERT)
  831         req.api_version_request = mv.get_api_version(
  832             mv.VOLUME_REVERT)
  833         # update volume's status failed
  834         mock_update.side_effect = [False, True]
  835 
  836         self.assertRaises(webob.exc.HTTPConflict, self.controller.revert,
  837                           req, fake_volume['id'], {'revert': {'snapshot_id':
  838                                                    fake_snapshot['id']}})
  839 
  840         # update snapshot's status failed
  841         mock_update.side_effect = [True, False]
  842 
  843         self.assertRaises(webob.exc.HTTPConflict, self.controller.revert,
  844                           req, fake_volume['id'], {'revert': {'snapshot_id':
  845                                                    fake_snapshot['id']}})
  846 
  847     @mock.patch.object(objects.Volume, 'get_latest_snapshot')
  848     @mock.patch.object(volume_api.API, 'get_volume')
  849     def test_volume_revert_with_not_equal_size(self, mock_volume,
  850                                                mock_latest):
  851         fake_volume = self._fake_create_volume(size=2)
  852         fake_snapshot = self._fake_create_snapshot(fake_volume['id'],
  853                                                    volume_size=1)
  854         mock_volume.return_value = fake_volume
  855         mock_latest.return_value = fake_snapshot
  856         req = fakes.HTTPRequest.blank('/v3/volumes/%s/revert'
  857                                       % fake_volume['id'])
  858         req.headers = mv.get_mv_header(mv.VOLUME_REVERT)
  859         req.api_version_request = mv.get_api_version(
  860             mv.VOLUME_REVERT)
  861         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.revert,
  862                           req, fake_volume['id'],
  863                           {'revert': {'snapshot_id': fake_snapshot['id']}})
  864 
  865     def test_view_get_attachments(self):
  866         fake_volume = self._fake_create_volume()
  867         fake_volume['attach_status'] = fields.VolumeAttachStatus.ATTACHING
  868         att_time = datetime.datetime(2017, 8, 31, 21, 55, 7,
  869                                      tzinfo=iso8601.UTC)
  870         a1 = {
  871             'id': fake.UUID1,
  872             'volume_id': fake.UUID2,
  873             'instance': None,
  874             'attached_host': None,
  875             'mountpoint': None,
  876             'attach_time': None,
  877             'attach_status': fields.VolumeAttachStatus.ATTACHING
  878         }
  879         a2 = {
  880             'id': fake.UUID3,
  881             'volume_id': fake.UUID4,
  882             'instance_uuid': fake.UUID5,
  883             'attached_host': 'host1',
  884             'mountpoint': 'na',
  885             'attach_time': att_time,
  886             'attach_status': fields.VolumeAttachStatus.ATTACHED
  887         }
  888         attachment1 = objects.VolumeAttachment(self.ctxt, **a1)
  889         attachment2 = objects.VolumeAttachment(self.ctxt, **a2)
  890         atts = {'objects': [attachment1, attachment2]}
  891         attachments = objects.VolumeAttachmentList(self.ctxt, **atts)
  892 
  893         fake_volume['volume_attachment'] = attachments
  894 
  895         # get_attachments should only return attachments with the
  896         # attached status = ATTACHED
  897         attachments = ViewBuilder()._get_attachments(fake_volume)
  898 
  899         self.assertEqual(1, len(attachments))
  900         self.assertEqual(fake.UUID3, attachments[0]['attachment_id'])
  901         self.assertEqual(fake.UUID4, attachments[0]['volume_id'])
  902         self.assertEqual(fake.UUID5, attachments[0]['server_id'])
  903         self.assertEqual('host1', attachments[0]['host_name'])
  904         self.assertEqual('na', attachments[0]['device'])
  905         self.assertEqual(att_time, attachments[0]['attached_at'])