Browse Source

Merge "Add update share-type API to Share Types"

changes/12/681512/1
Zuul 1 week ago
parent
commit
2a97de623f

+ 62
- 0
api-ref/source/parameters.yaml View File

@@ -814,6 +814,13 @@ create_share_from_snapshot_support:
814 814
   required: false
815 815
   type: boolean
816 816
   min_version: 2.24
817
+create_share_from_snapshot_support_body:
818
+  description: |
819
+    Boolean extra spec used for filtering of back ends by
820
+    their capability to create shares from snapshots.
821
+  in: body
822
+  required: false
823
+  type: boolean
817 824
 created_at:
818 825
   description: |
819 826
     The date and time stamp when the resource was created within the service's
@@ -1195,6 +1202,14 @@ is_default_type:
1195 1202
   required: true
1196 1203
   type: boolean
1197 1204
   min_version: 2.46
1205
+is_default_type_body:
1206
+  description: |
1207
+    Defines the share type created is default or not. If the returning
1208
+    value is true, then it is the default share type, otherwise, it is
1209
+    not default.
1210
+  in: body
1211
+  required: true
1212
+  type: boolean
1198 1213
 is_group_type_default:
1199 1214
   description: |
1200 1215
     Defines the share group type created is default or not. If the
@@ -1403,6 +1418,13 @@ mount_snapshot_support:
1403 1418
   required: false
1404 1419
   type: boolean
1405 1420
   min_version: 2.32
1421
+mount_snapshot_support_body:
1422
+  description: |
1423
+    Boolean extra spec used for filtering of back ends
1424
+    by their capability to mount share snapshots.
1425
+  in: body
1426
+  required: false
1427
+  type: boolean
1406 1428
 name:
1407 1429
   description: |
1408 1430
     The user defined name of the resource.
@@ -1775,6 +1797,12 @@ replication_type:
1775 1797
   required: false
1776 1798
   type: string
1777 1799
   min_version: 2.11
1800
+replication_type_body:
1801
+  description: |
1802
+    The share replication type.
1803
+  in: body
1804
+  required: false
1805
+  type: string
1778 1806
 request_id_body:
1779 1807
   description: |
1780 1808
     The UUID of the request during which the message was created.
@@ -1814,6 +1842,13 @@ revert_to_snapshot_support:
1814 1842
   required: false
1815 1843
   type: boolean
1816 1844
   min_version: 2.27
1845
+revert_to_snapshot_support_body:
1846
+  description: |
1847
+    Boolean extra spec used for filtering of back ends by their
1848
+    capability to revert shares to snapshots.
1849
+  in: body
1850
+  required: false
1851
+  type: boolean
1817 1852
 security_service_dns_ip:
1818 1853
   description: |
1819 1854
     The DNS IP address that is used inside the project network.
@@ -2371,6 +2406,21 @@ share_type_access:is_public:
2371 2406
   required: false
2372 2407
   type: boolean
2373 2408
   min_version: 2.7
2409
+share_type_access:is_public_body:
2410
+  description: |
2411
+    Indicates whether a share type is accessible by all projects (tenants)
2412
+    in the cloud.
2413
+  in: body
2414
+  required: true
2415
+  type: boolean
2416
+share_type_access:is_public_update_request:
2417
+  description: |
2418
+    Indicates whether the share type should be accessible by all projects
2419
+    (tenants) in the cloud. If not specified, the visibility of the share
2420
+    type is not altered.
2421
+  in: body
2422
+  required: false
2423
+  type: boolean
2374 2424
 share_type_description:
2375 2425
   description: |
2376 2426
     The description of the share type.
@@ -2378,6 +2428,12 @@ share_type_description:
2378 2428
   required: true
2379 2429
   type: string
2380 2430
   min_version: 2.41
2431
+share_type_description_body:
2432
+  description: |
2433
+    The description of the share type.
2434
+  in: body
2435
+  required: true
2436
+  type: string
2381 2437
 share_type_description_request:
2382 2438
   description: |
2383 2439
     The description of the share type. The value of this field is limited to
@@ -2386,6 +2442,12 @@ share_type_description_request:
2386 2442
   required: false
2387 2443
   type: string
2388 2444
   min_version: 2.41
2445
+share_type_description_update_request:
2446
+  description: |
2447
+    New description for the share type.
2448
+  in: body
2449
+  required: false
2450
+  type: string
2389 2451
 share_type_id_body:
2390 2452
   description: |
2391 2453
     The UUID of the share type.

+ 8
- 0
api-ref/source/samples/share-type-update-request.json View File

@@ -0,0 +1,8 @@
1
+{
2
+  "share_type":
3
+  {
4
+    "share_type_access:is_public": true,
5
+    "name": "testing",
6
+    "description": "share type description"
7
+  }
8
+}

+ 38
- 0
api-ref/source/samples/share-type-update-response.json View File

@@ -0,0 +1,38 @@
1
+{
2
+    "share_type": {
3
+        "required_extra_specs": {
4
+            "driver_handles_share_servers": true
5
+        },
6
+        "share_type_access:is_public": true,
7
+        "extra_specs": {
8
+            "replication_type": "readable",
9
+            "driver_handles_share_servers": "True",
10
+            "mount_snapshot_support": "False",
11
+            "revert_to_snapshot_support": "False",
12
+            "create_share_from_snapshot_support": "True",
13
+            "snapshot_support": "True"
14
+        },
15
+        "id": "7fa1342b-de9d-4d89-bdc8-af67795c0e52",
16
+        "name": "testing",
17
+        "is_default": false,
18
+        "description": "share type description"
19
+    },
20
+    "volume_type": {
21
+        "required_extra_specs": {
22
+            "driver_handles_share_servers": true
23
+        },
24
+        "share_type_access:is_public": true,
25
+        "extra_specs": {
26
+            "replication_type": "readable",
27
+            "driver_handles_share_servers": "True",
28
+            "mount_snapshot_support": "False",
29
+            "revert_to_snapshot_support": "False",
30
+            "create_share_from_snapshot_support": "True",
31
+            "snapshot_support": "True"
32
+        },
33
+        "id": "7fa1342b-de9d-4d89-bdc8-af67795c0e52",
34
+        "name": "testing",
35
+        "is_default": false,
36
+        "description": "share type description"
37
+    }
38
+}

+ 70
- 0
api-ref/source/share-types.inc View File

@@ -602,3 +602,73 @@ Request
602 602
 
603 603
    - project_id: project_id_path
604 604
    - share_type_id: share_type_id
605
+
606
+
607
+Update share type (since API v2.50)
608
+===================================
609
+
610
+.. rest_method::  PUT /v2/{project_id}/types/{share_type_id}
611
+
612
+.. versionadded:: 2.50
613
+
614
+Update a share type. Share type extra-specs cannot be updated
615
+with this API. Please use the respective APIs to `set extra specs
616
+<#set-extra-spec-for-share-type>`_ or `unset extra specs
617
+<#unset-an-extra-spec>`_.
618
+
619
+Response codes
620
+--------------
621
+
622
+.. rest_status_code:: success status.yaml
623
+
624
+   - 200
625
+
626
+.. rest_status_code:: error status.yaml
627
+
628
+   - 400
629
+   - 401
630
+   - 403
631
+   - 404
632
+   - 409
633
+
634
+Request
635
+-------
636
+
637
+.. rest_parameters:: parameters.yaml
638
+
639
+   - project_id: project_id_path
640
+   - share_type_id: share_type_id
641
+   - name: share_type_name_request
642
+   - share_type_access:is_public: share_type_access:is_public_update_request
643
+   - description: share_type_description_update_request
644
+
645
+Request example
646
+---------------
647
+
648
+.. literalinclude:: samples/share-type-update-request.json
649
+   :language: javascript
650
+
651
+Response parameters
652
+-------------------
653
+
654
+.. rest_parameters:: parameters.yaml
655
+
656
+   - id: share_type_id_body
657
+   - required_extra_specs: required_extra_specs
658
+   - extra_specs: extra_specs
659
+   - driver_handles_share_servers: driver_handles_share_servers
660
+   - snapshot_support: snapshot_support_1
661
+   - share_type_access:is_public: share_type_access:is_public_body
662
+   - name: share_type_name
663
+   - replication_type: replication_type_body
664
+   - mount_snapshot_support: mount_snapshot_support_body
665
+   - revert_to_snapshot_support: revert_to_snapshot_support_body
666
+   - create_share_from_snapshot_support: create_share_from_snapshot_support_body
667
+   - description: share_type_description_body
668
+   - is_default: is_default_type_body
669
+
670
+Response example
671
+----------------
672
+
673
+.. literalinclude:: samples/share-type-update-response.json
674
+   :language: javascript

+ 4
- 1
manila/api/openstack/api_version_request.py View File

@@ -134,13 +134,16 @@ REST_API_VERSION_HISTORY = """
134 134
     * 2.49 - Added Manage/Unmanage Share Server APIs. Updated Manage/Unmanage
135 135
              Shares and Snapshots APIs to work in
136 136
              ``driver_handles_shares_servers`` enabled mode.
137
+    * 2.50 - Added update share type API to Share Type APIs. Through this API
138
+             we can update the ``name``, ``description`` and/or
139
+             ``share_type_access:is_public`` fields of the share type.
137 140
 """
138 141
 
139 142
 # The minimum and maximum versions of the API supported
140 143
 # The default api version request is defined to be the
141 144
 # minimum version of the API supported.
142 145
 _MIN_API_VERSION = "2.0"
143
-_MAX_API_VERSION = "2.49"
146
+_MAX_API_VERSION = "2.50"
144 147
 DEFAULT_API_VERSION = _MIN_API_VERSION
145 148
 
146 149
 

+ 6
- 0
manila/api/openstack/rest_api_version_history.rst View File

@@ -275,3 +275,9 @@ user documentation.
275 275
 -----------------------
276 276
   Added Manage/Unmanage Share Server APIs. Updated Manage/Unmanage Shares and
277 277
   Snapshots APIs to work in ``driver_handles_shares_servers`` enabled mode.
278
+
279
+2.50
280
+----
281
+  Added update share type API to Share Type APIs. We can update the ``name``,
282
+  ``description`` and/or ``share_type_access:is_public`` fields of the share
283
+  type by the update share type API.

+ 87
- 6
manila/api/v2/share_types.py View File

@@ -52,6 +52,10 @@ class ShareTypesController(wsgi.Controller):
52 52
     def _notify_share_type_error(self, context, method, payload):
53 53
         rpc.get_notifier('shareType').error(context, method, payload)
54 54
 
55
+    def _notify_share_type_info(self, context, method, share_type):
56
+        payload = dict(share_types=share_type)
57
+        rpc.get_notifier('shareType').info(context, method, payload)
58
+
55 59
     def _check_body(self, body, action_name):
56 60
         if not self.is_valid_body(body, action_name):
57 61
             raise webob.exc.HTTPBadRequest()
@@ -221,9 +225,8 @@ class ShareTypesController(wsgi.Controller):
221 225
             share_type = share_types.get_share_type_by_name(context, name)
222 226
             share_type['required_extra_specs'] = required_extra_specs
223 227
             req.cache_db_share_type(share_type)
224
-            notifier_info = dict(share_types=share_type)
225
-            rpc.get_notifier('shareType').info(
226
-                context, 'share_type.create', notifier_info)
228
+            self._notify_share_type_info(
229
+                context, 'share_type.create', share_type)
227 230
 
228 231
         except exception.InvalidExtraSpec as e:
229 232
             raise webob.exc.HTTPBadRequest(explanation=six.text_type(e))
@@ -252,9 +255,8 @@ class ShareTypesController(wsgi.Controller):
252 255
         try:
253 256
             share_type = share_types.get_share_type(context, id)
254 257
             share_types.destroy(context, share_type['id'])
255
-            notifier_info = dict(share_types=share_type)
256
-            rpc.get_notifier('shareType').info(
257
-                context, 'share_type.delete', notifier_info)
258
+            self._notify_share_type_info(
259
+                context, 'share_type.delete', share_type)
258 260
         except exception.ShareTypeInUse as err:
259 261
             notifier_err = dict(id=id, error_message=six.text_type(err))
260 262
             self._notify_share_type_error(context, 'share_type.delete',
@@ -270,6 +272,85 @@ class ShareTypesController(wsgi.Controller):
270 272
 
271 273
         return webob.Response(status_int=http_client.ACCEPTED)
272 274
 
275
+    @wsgi.Controller.api_version("2.50")
276
+    @wsgi.action("update")
277
+    @wsgi.Controller.authorize
278
+    def update(self, req, id, body):
279
+        """Update name description is_public for a given share type."""
280
+        context = req.environ['manila.context']
281
+
282
+        if (not self.is_valid_body(body, 'share_type') and
283
+                not self.is_valid_body(body, 'volume_type')):
284
+            raise webob.exc.HTTPBadRequest()
285
+
286
+        elif self.is_valid_body(body, 'share_type'):
287
+            sha_type = body['share_type']
288
+        else:
289
+            sha_type = body['volume_type']
290
+        name = sha_type.get('name')
291
+        description = sha_type.get('description')
292
+        is_public = sha_type.get('share_type_access:is_public', None)
293
+
294
+        if is_public is not None:
295
+            try:
296
+                is_public = strutils.bool_from_string(is_public, strict=True)
297
+            except ValueError:
298
+                msg = _("share_type_access:is_public has a non-boolean"
299
+                        " value.")
300
+                raise webob.exc.HTTPBadRequest(explanation=msg)
301
+
302
+        # If name specified, name can not be empty or greater than 255.
303
+        if name is not None:
304
+            if len(name.strip()) == 0:
305
+                msg = _("Share type name cannot be empty.")
306
+                raise webob.exc.HTTPBadRequest(explanation=msg)
307
+            if len(name) > 255:
308
+                msg = _("Share type name cannot be greater than 255 "
309
+                        "characters in length.")
310
+                raise webob.exc.HTTPBadRequest(explanation=msg)
311
+
312
+        # If description specified, length can not greater than 255.
313
+        if description and len(description) > 255:
314
+            msg = _("Share type description cannot be greater than 255 "
315
+                    "characters in length.")
316
+            raise webob.exc.HTTPBadRequest(explanation=msg)
317
+
318
+        # Name, description and is_public can not be None.
319
+        # Specify one of them, or a combination thereof.
320
+        if name is None and description is None and is_public is None:
321
+            msg = _("Specify share type name, description, "
322
+                    "share_type_access:is_public or a combination thereof.")
323
+            raise webob.exc.HTTPBadRequest(explanation=msg)
324
+
325
+        try:
326
+            share_types.update(context, id, name, description,
327
+                               is_public=is_public)
328
+            # Get the updated
329
+            sha_type = self._show_share_type_details(context, id)
330
+            req.cache_resource(sha_type, name='types')
331
+            self._notify_share_type_info(
332
+                context, 'share_type.update', sha_type)
333
+
334
+        except exception.ShareTypeNotFound as err:
335
+            notifier_err = {"id": id, "error_message": err}
336
+            self._notify_share_type_error(
337
+                context, 'share_type.update', notifier_err)
338
+            # Not found exception will be handled at the wsgi level
339
+            raise
340
+        except exception.ShareTypeExists as err:
341
+            notifier_err = {"share_type": sha_type, "error_message": err}
342
+            self._notify_share_type_error(
343
+                context, 'share_type.update', notifier_err)
344
+            raise webob.exc.HTTPConflict(explanation=err.msg)
345
+        except exception.ShareTypeUpdateFailed as err:
346
+            notifier_err = {"share_type": sha_type, "error_message": err}
347
+            self._notify_share_type_error(
348
+                context, 'share_type.update', notifier_err)
349
+            raise webob.exc.HTTPInternalServerError(
350
+                explanation=err.msg)
351
+
352
+        return self._view_builder.show(req, sha_type)
353
+
273 354
     @wsgi.Controller.authorize('list_project_access')
274 355
     def share_type_access(self, req, id):
275 356
         context = req.environ['manila.context']

+ 5
- 0
manila/db/api.py View File

@@ -949,6 +949,11 @@ def share_type_create(context, values, projects=None):
949 949
     return IMPL.share_type_create(context, values, projects)
950 950
 
951 951
 
952
+def share_type_update(context, share_type_id, values):
953
+    """Update an exist share type."""
954
+    return IMPL.share_type_update(context, share_type_id, values)
955
+
956
+
952 957
 def share_type_get_all(context, inactive=False, filters=None):
953 958
     """Get all share types.
954 959
 

+ 51
- 0
manila/db/sqlalchemy/api.py View File

@@ -210,6 +210,18 @@ def apply_sorting(model, query, sort_key, sort_dir):
210 210
     return query.order_by(sort_method())
211 211
 
212 212
 
213
+def handle_db_data_error(f):
214
+    def wrapper(*args, **kwargs):
215
+        try:
216
+            return f(*args, **kwargs)
217
+        except db_exc.DBDataError:
218
+            msg = _('Error writing field to database.')
219
+            LOG.exception(msg)
220
+            raise exception.Invalid(msg)
221
+
222
+    return wrapper
223
+
224
+
213 225
 def model_query(context, model, *args, **kwargs):
214 226
     """Query helper that accounts for context's `read_deleted` field.
215 227
 
@@ -3944,6 +3956,45 @@ def _share_type_get_query(context, session=None, read_deleted=None,
3944 3956
     return query
3945 3957
 
3946 3958
 
3959
+@handle_db_data_error
3960
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
3961
+def _type_update(context, type_id, values, is_group):
3962
+
3963
+    if values.get('name') is None:
3964
+        values.pop('name', None)
3965
+
3966
+    if is_group:
3967
+        model = models.ShareGroupTypes
3968
+        exists_exc = exception.ShareGroupTypeExists
3969
+        exists_args = {'type_id': values.get('name')}
3970
+    else:
3971
+        model = models.ShareTypes
3972
+        exists_exc = exception.ShareTypeExists
3973
+        exists_args = {'id': values.get('name')}
3974
+
3975
+    session = get_session()
3976
+    with session.begin():
3977
+        query = model_query(context, model, session=session)
3978
+
3979
+        try:
3980
+            result = query.filter_by(id=type_id).update(values)
3981
+        except db_exception.DBDuplicateEntry:
3982
+            # This exception only occurs if there's a non-deleted
3983
+            # share/group type which has the same name as the name being
3984
+            # updated.
3985
+            raise exists_exc(**exists_args)
3986
+
3987
+        if not result:
3988
+            if is_group:
3989
+                raise exception.ShareGroupTypeNotFound(type_id=type_id)
3990
+            else:
3991
+                raise exception.ShareTypeNotFound(share_type_id=type_id)
3992
+
3993
+
3994
+def share_type_update(context, share_type_id, values):
3995
+    _type_update(context, share_type_id, values, is_group=False)
3996
+
3997
+
3947 3998
 @require_context
3948 3999
 def share_type_get_all(context, inactive=False, filters=None):
3949 4000
     """Returns a dict describing all share_types with name as key."""

+ 4
- 0
manila/exception.py View File

@@ -681,6 +681,10 @@ class ShareTypeCreateFailed(ManilaException):
681 681
                 "name %(name)s and specs %(extra_specs)s.")
682 682
 
683 683
 
684
+class ShareTypeUpdateFailed(ManilaException):
685
+    message = _("Cannot update share_type %(id)s.")
686
+
687
+
684 688
 class ShareGroupTypeCreateFailed(ManilaException):
685 689
     message = _("Cannot create share group type with "
686 690
                 "name %(name)s and specs %(group_specs)s.")

+ 10
- 0
manila/policies/share_type.py View File

@@ -31,6 +31,16 @@ share_type_policies = [
31 31
                 'path': '/types',
32 32
             }
33 33
         ]),
34
+    policy.DocumentedRuleDefault(
35
+        name=BASE_POLICY_NAME % 'update',
36
+        check_str=base.RULE_ADMIN_API,
37
+        description='Update share type.',
38
+        operations=[
39
+            {
40
+                'method': 'PUT',
41
+                'path': '/types/{share_type_id}',
42
+            }
43
+        ]),
34 44
     policy.DocumentedRuleDefault(
35 45
         name=BASE_POLICY_NAME % 'show',
36 46
         check_str=base.RULE_DEFAULT,

+ 18
- 0
manila/share/share_types.py View File

@@ -70,6 +70,24 @@ def sanitize_extra_specs(extra_specs):
70 70
     return extra_specs
71 71
 
72 72
 
73
+def update(context, id, name, description, is_public=None):
74
+    """Update share type by id."""
75
+    values = {}
76
+    if name:
77
+        values.update({'name': name})
78
+    if description == "":
79
+        values.update({'description': None})
80
+    elif description:
81
+        values.update({'description': description})
82
+    if is_public is not None:
83
+        values.update({'is_public': is_public})
84
+    try:
85
+        db.share_type_update(context, id, values)
86
+    except db_exception.DBError:
87
+        LOG.exception('DB error.')
88
+        raise exception.ShareTypeUpdateFailed(id=id)
89
+
90
+
73 91
 def destroy(context, id):
74 92
     """Marks share types as deleted."""
75 93
     if id is None:

+ 143
- 7
manila/tests/api/v2/test_share_types.py View File

@@ -19,6 +19,7 @@ import ddt
19 19
 import mock
20 20
 from oslo_config import cfg
21 21
 from oslo_utils import timeutils
22
+import random
22 23
 import webob
23 24
 
24 25
 from manila.api.v2 import share_types as types
@@ -45,15 +46,25 @@ def stub_share_type(id):
45 46
         "key5": "value5",
46 47
         constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS: "true",
47 48
     }
48
-    return dict(
49
-        id=id,
50
-        name='share_type_%s' % str(id),
51
-        description='description_%s' % str(id),
52
-        extra_specs=specs,
53
-        required_extra_specs={
49
+    if id == 4:
50
+        name = 'update_share_type_%s' % str(id)
51
+        description = 'update_description_%s' % str(id)
52
+        is_public = False
53
+    else:
54
+        name = 'share_type_%s' % str(id)
55
+        description = 'description_%s' % str(id)
56
+        is_public = True
57
+    share_type = {
58
+        'id': id,
59
+        'name': name,
60
+        'description': description,
61
+        'is_public': is_public,
62
+        'extra_specs': specs,
63
+        'required_extra_specs': {
54 64
             constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS: "true",
55 65
         }
56
-    )
66
+    }
67
+    return share_type
57 68
 
58 69
 
59 70
 def return_share_types_get_all_types(context, search_opts=None):
@@ -102,6 +113,20 @@ def return_share_types_get_share_type(context, id=1):
102 113
     return stub_share_type(int(id))
103 114
 
104 115
 
116
+def return_share_type_update(context, id=4, name=None, description=None,
117
+                             is_public=None):
118
+    if id == 888:
119
+        raise exception.ShareTypeUpdateFailed(id=id)
120
+    if id == 999:
121
+        raise exception.ShareTypeNotFound(share_type_id=id)
122
+    pre_share_type = stub_share_type(int(id))
123
+    new_name = name
124
+    new_description = description
125
+    return pre_share_type.update({"name": new_name,
126
+                                  "description": new_description,
127
+                                  "is_public": is_public})
128
+
129
+
105 130
 def return_share_types_get_by_name(context, name):
106 131
     if name == "777":
107 132
         raise exception.ShareTypeNotFoundByName(share_type_name=name)
@@ -146,6 +171,28 @@ def make_create_body(name="test_share_1", extra_specs=None,
146 171
     return body
147 172
 
148 173
 
174
+def generate_long_description(des_length=256):
175
+    random_str = ''
176
+    base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz'
177
+    length = len(base_str) - 1
178
+    for i in range(des_length):
179
+        random_str += base_str[random.randint(0, length)]
180
+    return random_str
181
+
182
+
183
+def make_update_body(name=None, description=None, is_public=None):
184
+    body = {"share_type": {}}
185
+    if name:
186
+        body["share_type"].update({"name": name})
187
+    if description:
188
+        body["share_type"].update({"description": description})
189
+    if is_public is not None:
190
+        body["share_type"].update(
191
+            {"share_type_access:is_public": is_public})
192
+
193
+    return body
194
+
195
+
149 196
 @ddt.ddt
150 197
 class ShareTypesAPITest(test.TestCase):
151 198
 
@@ -167,6 +214,9 @@ class ShareTypesAPITest(test.TestCase):
167 214
         self.mock_object(
168 215
             share_types, 'get_share_type',
169 216
             mock.Mock(side_effect=return_share_types_get_share_type))
217
+        self.mock_object(
218
+            share_types, 'update',
219
+            mock.Mock(side_effect=return_share_type_update))
170 220
         self.mock_object(
171 221
             share_types, 'destroy',
172 222
             mock.Mock(side_effect=return_share_types_destroy))
@@ -390,6 +440,92 @@ class ShareTypesAPITest(test.TestCase):
390 440
                           self.controller._parse_is_public,
391 441
                           'fakefakefake')
392 442
 
443
+    @ddt.data(
444
+        ("new_name", "new_description", "wrong_bool"),
445
+        (" ", "new_description", "true"),
446
+        (" ", generate_long_description(256), "true"),
447
+        (None, None, None),
448
+    )
449
+    @ddt.unpack
450
+    def test_share_types_update_with_invalid_parameter(
451
+            self, name, description, is_public):
452
+        req = fakes.HTTPRequest.blank('/v2/fake/types/4',
453
+                                      version='2.50')
454
+        body = make_update_body(name, description, is_public)
455
+        self.assertRaises(webob.exc.HTTPBadRequest,
456
+                          self.controller.update,
457
+                          req, 4, body)
458
+        self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))
459
+
460
+    def test_share_types_update_with_invalid_body(self):
461
+        req = fakes.HTTPRequest.blank('/v2/fake/types/4',
462
+                                      version='2.50')
463
+        body = {'share_type': 'i_am_invalid_body'}
464
+        self.assertRaises(webob.exc.HTTPBadRequest,
465
+                          self.controller.update,
466
+                          req, 4, body)
467
+        self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))
468
+
469
+    def test_share_types_update(self):
470
+        req = fakes.HTTPRequest.blank('/v2/fake/types/4',
471
+                                      version='2.50')
472
+        self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))
473
+        body = make_update_body("update_share_type_4",
474
+                                "update_description_4",
475
+                                is_public=False)
476
+        res_dict = self.controller.update(req, 4, body)
477
+        self.assertEqual(1, len(fake_notifier.NOTIFICATIONS))
478
+        self.assertEqual(2, len(res_dict))
479
+
480
+        self.assertEqual('update_share_type_4', res_dict['share_type']['name'])
481
+        self.assertEqual('update_share_type_4',
482
+                         res_dict['volume_type']['name'])
483
+        self.assertIs(False,
484
+                      res_dict['share_type']['share_type_access:is_public'])
485
+
486
+        self.assertEqual('update_description_4',
487
+                         res_dict['share_type']['description'])
488
+        self.assertEqual('update_description_4',
489
+                         res_dict['volume_type']['description'])
490
+
491
+    def test_share_types_update_pre_v250(self):
492
+        req = fakes.HTTPRequest.blank('/v2/fake/types/4',
493
+                                      version='2.49')
494
+        self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))
495
+        body = make_update_body("update_share_type_4",
496
+                                "update_description_4",
497
+                                is_public=False)
498
+        self.assertRaises(exception.VersionNotFoundForAPIMethod,
499
+                          self.controller.update,
500
+                          req, 4, body)
501
+        self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))
502
+
503
+    def test_share_types_update_failed(self):
504
+        self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))
505
+        req = fakes.HTTPRequest.blank('/v2/fake/types/888',
506
+                                      version='2.50')
507
+        body = make_update_body("update_share_type_888",
508
+                                "update_description_888",
509
+                                is_public=False)
510
+        self.assertRaises(webob.exc.HTTPInternalServerError,
511
+                          self.controller.update,
512
+                          req, 888, body)
513
+        self.assertEqual(1, len(fake_notifier.NOTIFICATIONS))
514
+
515
+    def test_share_types_update_not_found(self):
516
+        self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))
517
+        req = fakes.HTTPRequest.blank('/v2/fake/types/999',
518
+                                      version='2.50')
519
+
520
+        body = make_update_body("update_share_type_999",
521
+                                "update_description_999",
522
+                                is_public=False)
523
+
524
+        self.assertRaises(exception.ShareTypeNotFound,
525
+                          self.controller.update,
526
+                          req, 999, body)
527
+        self.assertEqual(1, len(fake_notifier.NOTIFICATIONS))
528
+
393 529
     def test_share_types_delete(self):
394 530
         req = fakes.HTTPRequest.blank('/v2/fake/types/1')
395 531
         self.assertEqual(0, len(fake_notifier.NOTIFICATIONS))

+ 34
- 0
manila/tests/db/sqlalchemy/test_api.py View File

@@ -3275,6 +3275,40 @@ class ShareTypeAPITestCase(test.TestCase):
3275 3275
         self.assertRaises(exception.DefaultShareTypeNotConfigured,
3276 3276
                           db_api.share_type_get, self.ctxt, None)
3277 3277
 
3278
+    @ddt.data(
3279
+        {'name': 'st_1', 'description': 'des_1', 'is_public': True},
3280
+        {'name': 'st_2', 'description': 'des_2', 'is_public': None},
3281
+        {'name': 'st_3', 'description': None, 'is_public': False},
3282
+        {'name': None, 'description': 'des_4', 'is_public': True},
3283
+    )
3284
+    @ddt.unpack
3285
+    def test_share_type_update(self, name, description, is_public):
3286
+        values = {}
3287
+        if name:
3288
+            values.update({'name': name})
3289
+        if description:
3290
+            values.update({'description': description})
3291
+        if is_public is not None:
3292
+            values.update({'is_public': is_public})
3293
+        share_type = db_utils.create_share_type(name='st_name')
3294
+        db_api.share_type_update(self.ctxt, share_type['id'], values)
3295
+        updated_st = db_api.share_type_get_by_name_or_id(self.ctxt,
3296
+                                                         share_type['id'])
3297
+        if name:
3298
+            self.assertEqual(name, updated_st['name'])
3299
+        if description:
3300
+            self.assertEqual(description, updated_st['description'])
3301
+        if is_public is not None:
3302
+            self.assertEqual(is_public, updated_st['is_public'])
3303
+
3304
+    def test_share_type_update_not_found(self):
3305
+        share_type = db_utils.create_share_type(name='st_update_test')
3306
+        db_api.share_type_destroy(self.ctxt, share_type['id'])
3307
+        values = {"name": "not_exist"}
3308
+        self.assertRaises(exception.ShareTypeNotFound,
3309
+                          db_api.share_type_update,
3310
+                          self.ctxt, share_type['id'], values)
3311
+
3278 3312
 
3279 3313
 class MessagesDatabaseAPITestCase(test.TestCase):
3280 3314
 

+ 58
- 0
manila/tests/share/test_share_types.py View File

@@ -41,6 +41,28 @@ def create_share_type_dict(extra_specs=None):
41 41
     }
42 42
 
43 43
 
44
+def return_share_type_update(context, id, values):
45
+    name = values.get('name')
46
+    description = values.get('description')
47
+    is_public = values.get('is_public')
48
+    if id == '444':
49
+        raise exception.ShareTypeUpdateFailed(id=id)
50
+    else:
51
+        st_update = {
52
+            'created_at': datetime.datetime(2019, 9, 9, 14, 40, 31),
53
+            'deleted': '0',
54
+            'deleted_at': None,
55
+            'extra_specs': {u'gold': u'True'},
56
+            'required_extra_specs': {},
57
+            'id': id,
58
+            'name': name,
59
+            'is_public': is_public,
60
+            'description': description,
61
+            'updated_at': None
62
+        }
63
+        return st_update
64
+
65
+
44 66
 @ddt.ddt
45 67
 class ShareTypesTestCase(test.TestCase):
46 68
 
@@ -71,6 +93,21 @@ class ShareTypesTestCase(test.TestCase):
71 93
         }
72 94
     }
73 95
 
96
+    fake_type_update = {
97
+        'test_type_update': {
98
+            'created_at': datetime.datetime(2019, 9, 9, 14, 40, 31),
99
+            'deleted': '0',
100
+            'deleted_at': None,
101
+            'extra_specs': {u'gold': u'True'},
102
+            'required_extra_specs': {},
103
+            'id': '888',
104
+            'name': 'new_name',
105
+            'is_public': True,
106
+            'description': 'new_description',
107
+            'updated_at': None
108
+        }
109
+    }
110
+
74 111
     fake_r_extra_specs = {
75 112
         u'gold': u'True',
76 113
         u'driver_handles_share_servers': u'True'
@@ -254,6 +291,27 @@ class ShareTypesTestCase(test.TestCase):
254 291
         share_types.get_share_type_extra_specs.assert_called_once_with(
255 292
             self.fake_share_type_id)
256 293
 
294
+    def test_update_share_type(self):
295
+        expected = self.fake_type_update['test_type_update']
296
+        self.mock_object(db,
297
+                         'share_type_update',
298
+                         mock.Mock(side_effect=return_share_type_update))
299
+        self.mock_object(db,
300
+                         'share_type_get',
301
+                         mock.Mock(return_value=expected))
302
+        new_name = "new_name"
303
+        new_description = "new_description"
304
+        is_public = True
305
+        self.assertRaises(exception.ShareTypeUpdateFailed, share_types.update,
306
+                          self.context, id='444', name=new_name,
307
+                          description=new_description, is_public=is_public)
308
+        share_types.update(self.context, '888', new_name,
309
+                           new_description, is_public)
310
+        st_update = share_types.get_share_type(self.context, '888')
311
+        self.assertEqual(new_name, st_update['name'])
312
+        self.assertEqual(new_description, st_update['description'])
313
+        self.assertEqual(is_public, st_update['is_public'])
314
+
257 315
     @ddt.data({}, {"fake": "fake"})
258 316
     def test_create_without_required_extra_spec(self, optional_specs):
259 317
 

+ 5
- 0
releasenotes/notes/bp-update-share-type-name-or-description-a39c5991b930932f.yaml View File

@@ -0,0 +1,5 @@
1
+---
2
+features:
3
+  - The ``name``, ``description`` and/or ``share_type_access:is_public``
4
+    attributes of share types can be updated with API version ``2.50``
5
+    and beyond.

Loading…
Cancel
Save