Browse Source

Add update_access() method to driver interface

- Add update_access() method to driver interface
- Move all code related to access operations to ShareInstanceAccess
class
- Statuses from individual access rules are now mapped to
share_instance's access_rules_status
- Add 'access_rules_status' field to share instance, which indicates
current status of applying access rules

APIImpact
Co-Authored-By: Rodrigo Barbieri <rodrigo.barbieri@fit-tecnologia.org.br>
Co-Authored-By: Tiago Pasqualini da Silva <tiago.pasqualini@gmail.com>
Implements: bp new-share-access-driver-interface

Change-Id: Iff1ec2e3176a46e9f6bd383b38ffc5d838aa8bb8
tags/2.0.0.0b3
Igor Malinovskiy 3 years ago
parent
commit
b1b723ad0b
35 changed files with 1456 additions and 777 deletions
  1. 35
    26
      contrib/share_driver_hooks/zaqar_notification.py
  2. 19
    10
      contrib/share_driver_hooks/zaqar_notification_example_consumer.py
  3. 15
    12
      manila/api/openstack/api_version_request.py
  4. 4
    0
      manila/api/openstack/rest_api_version_history.rst
  5. 7
    0
      manila/api/views/share_instance.py
  6. 5
    0
      manila/api/views/shares.py
  7. 1
    0
      manila/common/constants.py
  8. 10
    3
      manila/db/api.py
  9. 133
    0
      manila/db/migrations/alembic/versions/344c1ac4747f_add_share_instance_access_rules_status.py
  10. 25
    16
      manila/db/sqlalchemy/api.py
  11. 62
    39
      manila/db/sqlalchemy/models.py
  12. 1
    1
      manila/exception.py
  13. 155
    0
      manila/share/access.py
  14. 40
    30
      manila/share/api.py
  15. 26
    0
      manila/share/driver.py
  16. 46
    79
      manila/share/manager.py
  17. 29
    92
      manila/share/migration.py
  18. 13
    5
      manila/share/rpcapi.py
  19. 1
    0
      manila/tests/api/contrib/stubs.py
  20. 98
    0
      manila/tests/db/migrations/alembic/migrations_data_checks.py
  21. 22
    32
      manila/tests/db/sqlalchemy/test_api.py
  22. 24
    0
      manila/tests/db/sqlalchemy/test_models.py
  23. 119
    0
      manila/tests/share/test_access.py
  24. 49
    46
      manila/tests/share/test_api.py
  25. 10
    0
      manila/tests/share/test_driver.py
  26. 73
    98
      manila/tests/share/test_manager.py
  27. 75
    168
      manila/tests/share/test_migration.py
  28. 3
    3
      manila/tests/share/test_rpcapi.py
  29. 1
    1
      manila_tempest_tests/config.py
  30. 24
    0
      manila_tempest_tests/services/share/v2/json/shares_client.py
  31. 7
    0
      manila_tempest_tests/tests/api/admin/test_share_instances.py
  32. 260
    98
      manila_tempest_tests/tests/api/test_rules.py
  33. 44
    17
      manila_tempest_tests/tests/api/test_rules_negative.py
  34. 14
    0
      manila_tempest_tests/tests/api/test_shares_actions.py
  35. 6
    1
      manila_tempest_tests/tests/scenario/manager_share.py

+ 35
- 26
contrib/share_driver_hooks/zaqar_notification.py View File

@@ -30,37 +30,46 @@ class ZaqarNotification(hook.HookBase):
30 30
     share_api = api.API()
31 31
 
32 32
     def _access_changed_trigger(self, context, func_name,
33
-                                access_id, share_instance_id):
34
-        access = self.share_api.access_get(context, access_id=access_id)
35
-        share = self.share_api.get(context, share_id=access.share_id)
36
-
37
-        for ins in share.instances:
38
-            if ins.id == share_instance_id:
39
-                share_instance = ins
40
-                break
41
-        else:
42
-            raise exception.InstanceNotFound(instance_id=share_instance_id)
43
-
44
-        for ins in access.instance_mappings:
45
-            if ins.share_instance_id == share_instance_id:
46
-                access_instance = ins
47
-                break
48
-        else:
49
-            raise exception.InstanceNotFound(instance_id=share_instance_id)
33
+                                access_rules_ids, share_instance_id):
34
+
35
+        access = [self.db.share_access_get(context, rule_id)
36
+                  for rule_id in access_rules_ids]
37
+
38
+        share_instance = self.db.share_instance_get(context, share_instance_id)
39
+
40
+        share = self.share_api.get(context, share_id=share_instance.share_id)
41
+
42
+        def rules_view(rules):
43
+            result = []
44
+
45
+            for rule in rules:
46
+                access_instance = None
47
+
48
+                for ins in rule.instance_mappings:
49
+                    if ins.share_instance_id == share_instance_id:
50
+                        access_instance = ins
51
+                        break
52
+                    else:
53
+                        raise exception.InstanceNotFound(
54
+                            instance_id=share_instance_id)
55
+
56
+                result.append({
57
+                    'access_id': rule.id,
58
+                    'access_instance_id': access_instance.id,
59
+                    'access_type': rule.access_type,
60
+                    'access_to': rule.access_to,
61
+                    'access_level': rule.access_level,
62
+                })
63
+            return result
50 64
 
51 65
         is_allow_operation = 'allow' in func_name
52 66
         results = {
53
-            'share_id': access.share_id,
67
+            'share_id': share.share_id,
54 68
             'share_instance_id': share_instance_id,
55 69
             'export_locations': [
56 70
                 el.path for el in share_instance.export_locations],
57 71
             'share_proto': share.share_proto,
58
-            'access_id': access.id,
59
-            'access_instance_id': access_instance.id,
60
-            'access_type': access.access_type,
61
-            'access_to': access.access_to,
62
-            'access_level': access.access_level,
63
-            'access_state': access_instance.state,
72
+            'access_rules': rules_view(access),
64 73
             'is_allow_operation': is_allow_operation,
65 74
             'availability_zone': share_instance.availability_zone,
66 75
         }
@@ -75,7 +84,7 @@ class ZaqarNotification(hook.HookBase):
75 84
             data = self._access_changed_trigger(
76 85
                 context,
77 86
                 func_name,
78
-                kwargs.get('access_id'),
87
+                kwargs.get('access_rules'),
79 88
                 kwargs.get('share_instance_id'),
80 89
             )
81 90
             self._send_notification(data)
@@ -89,7 +98,7 @@ class ZaqarNotification(hook.HookBase):
89 98
             data = self._access_changed_trigger(
90 99
                 context,
91 100
                 func_name,
92
-                kwargs.get('access_id'),
101
+                kwargs.get('access_rules'),
93 102
                 kwargs.get('share_instance_id'),
94 103
             )
95 104
             self._send_notification(data)

+ 19
- 10
contrib/share_driver_hooks/zaqar_notification_example_consumer.py View File

@@ -105,12 +105,17 @@ def handle_message(data):
105 105
 
106 106
     Expected structure of a message is following:
107 107
         {'data': {
108
-             'access_id': u'b28268b9-36c6-40d3-a485-22534077328f',
109
-             'access_instance_id': u'd137b2cb-f549-4141-9dd7-36b2789fb973',
110
-             'access_level': u'rw',
111
-             'access_state': u'active',
112
-             'access_to': u'7.7.7.7',
113
-             'access_type': u'ip',
108
+            'access_rules': [
109
+                 {
110
+                    'access_id': u'b28268b9-36c6-40d3-a485-22534077328f',
111
+                    'access_instance_id':
112
+                        u'd137b2cb-f549-4141-9dd7-36b2789fb973',
113
+                    'access_level': u'rw',
114
+                    'access_state': u'active',
115
+                    'access_to': u'7.7.7.7',
116
+                    'access_type': u'ip',
117
+                }
118
+              ],
114 119
              'availability_zone': u'nova',
115 120
              'export_locations': [u'127.0.0.1:/path/to/nfs/share'],
116 121
              'is_allow_operation': True,
@@ -121,10 +126,14 @@ def handle_message(data):
121 126
     """
122 127
     if 'data' in data.keys():
123 128
         data = data['data']
124
-    if (data.get('access_type', '?').lower() == 'ip' and
125
-            'access_state' in data.keys() and
126
-            'error' not in data.get('access_state', '?').lower() and
127
-            data.get('share_proto', '?').lower() == 'nfs'):
129
+
130
+    valid_access = (
131
+        'access_rules' in data and len(data['access_rules']) == 1 and
132
+        data['access_rules'][0].get('access_type', '?').lower() == 'ip' and
133
+        data.get('share_proto', '?').lower() == 'nfs'
134
+    )
135
+
136
+    if valid_access:
128 137
         is_allow_operation = data['is_allow_operation']
129 138
         export_location = data['export_locations'][0]
130 139
         if is_allow_operation:

+ 15
- 12
manila/api/openstack/api_version_request.py View File

@@ -44,24 +44,27 @@ REST_API_VERSION_HISTORY = """
44 44
 
45 45
     REST API Version History:
46 46
 
47
-    * 1.0 - Initial version. Includes all V1 APIs and extensions in Kilo.
48
-    * 2.0 - Versions API updated to reflect beginning of microversions epoch.
49
-    * 2.1 - Share create() doesn't ignore availability_zone field of share.
50
-    * 2.2 - Snapshots become optional feature.
51
-    * 2.3 - Share instances admin API
52
-    * 2.4 - Consistency Group support
53
-    * 2.5 - Share Migration admin API
54
-    * 2.6 - Return share_type UUID instead of name in Share API
55
-    * 2.7 - Rename old extension-like API URLs to core-API-like
56
-    * 2.8 - Attr "is_public" can be set for share using API "manage"
57
-    * 2.9 - Add export locations API
47
+    * 1.0  - Initial version. Includes all V1 APIs and extensions in Kilo.
48
+    * 2.0  - Versions API updated to reflect beginning of microversions epoch.
49
+    * 2.1  - Share create() doesn't ignore availability_zone field of share.
50
+    * 2.2  - Snapshots become optional feature.
51
+    * 2.3  - Share instances admin API
52
+    * 2.4  - Consistency Group support
53
+    * 2.5  - Share Migration admin API
54
+    * 2.6  - Return share_type UUID instead of name in Share API
55
+    * 2.7  - Rename old extension-like API URLs to core-API-like
56
+    * 2.8  - Attr "is_public" can be set for share using API "manage"
57
+    * 2.9  - Add export locations API
58
+    * 2.10 - Field 'access_rules_status' was added to shares and share
59
+            instances.
60
+
58 61
 """
59 62
 
60 63
 # The minimum and maximum versions of the API supported
61 64
 # The default api version request is defined to be the
62 65
 # the minimum version of the API supported.
63 66
 _MIN_API_VERSION = "2.0"
64
-_MAX_API_VERSION = "2.9"
67
+_MAX_API_VERSION = "2.10"
65 68
 DEFAULT_API_VERSION = _MIN_API_VERSION
66 69
 
67 70
 

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

@@ -74,3 +74,7 @@ user documentation.
74 74
 ---
75 75
   Add export locations API. Remove export locations from "shares" and
76 76
   "share instances" APIs.
77
+
78
+2.10
79
+----
80
+  Field 'access_rules_status' was added to shares and share instances.

+ 7
- 0
manila/api/views/share_instance.py View File

@@ -20,6 +20,7 @@ class ViewBuilder(common.ViewBuilder):
20 20
 
21 21
     _detail_version_modifiers = [
22 22
         "remove_export_locations",
23
+        "add_access_rules_status_field",
23 24
     ]
24 25
 
25 26
     def detail_list(self, request, instances):
@@ -64,3 +65,9 @@ class ViewBuilder(common.ViewBuilder):
64 65
     def remove_export_locations(self, share_instance_dict, share_instance):
65 66
         share_instance_dict.pop('export_location')
66 67
         share_instance_dict.pop('export_locations')
68
+
69
+    @common.ViewBuilder.versioned_method("2.10")
70
+    def add_access_rules_status_field(self, instance_dict, share_instance):
71
+        instance_dict['access_rules_status'] = (
72
+            share_instance.get('access_rules_status')
73
+        )

+ 5
- 0
manila/api/views/shares.py View File

@@ -26,6 +26,7 @@ class ViewBuilder(common.ViewBuilder):
26 26
         "add_task_state_field",
27 27
         "modify_share_type_field",
28 28
         "remove_export_locations",
29
+        "add_access_rules_status_field",
29 30
     ]
30 31
 
31 32
     def summary_list(self, request, shares):
@@ -123,6 +124,10 @@ class ViewBuilder(common.ViewBuilder):
123 124
         share_dict.pop('export_location')
124 125
         share_dict.pop('export_locations')
125 126
 
127
+    @common.ViewBuilder.versioned_method("2.10")
128
+    def add_access_rules_status_field(self, share_dict, share):
129
+        share_dict['access_rules_status'] = share.get('access_rules_status')
130
+
126 131
     def _list_view(self, func, request, shares):
127 132
         """Provide a view for a list of shares."""
128 133
         shares_list = [func(request, share)['share'] for share in shares]

+ 1
- 0
manila/common/constants.py View File

@@ -22,6 +22,7 @@ STATUS_ERROR_DELETING = 'error_deleting'
22 22
 STATUS_AVAILABLE = 'available'
23 23
 STATUS_ACTIVE = 'active'
24 24
 STATUS_INACTIVE = 'inactive'
25
+STATUS_OUT_OF_SYNC = 'out_of_sync'
25 26
 STATUS_MANAGING = 'manage_starting'
26 27
 STATUS_MANAGE_ERROR = 'manage_error'
27 28
 STATUS_UNMANAGING = 'unmanage_starting'

+ 10
- 3
manila/db/api.py View File

@@ -435,6 +435,12 @@ def share_instance_access_get_all(context, access_id, session=None):
435 435
     return IMPL.share_instance_access_get_all(context, access_id, session=None)
436 436
 
437 437
 
438
+def share_access_get_all_for_instance(context, instance_id, session=None):
439
+    """Get all access rules related to a certain share instance."""
440
+    return IMPL.share_access_get_all_for_instance(
441
+        context, instance_id, session=None)
442
+
443
+
438 444
 def share_access_get_all_by_type_and_access(context, share_id, access_type,
439 445
                                             access):
440 446
     """Returns share access by given type and access."""
@@ -452,9 +458,10 @@ def share_instance_access_delete(context, mapping_id):
452 458
     return IMPL.share_instance_access_delete(context, mapping_id)
453 459
 
454 460
 
455
-def share_instance_access_update_state(context, mapping_id, state):
456
-    """Update state of access rule mapping."""
457
-    return IMPL.share_instance_access_update_state(context, mapping_id, state)
461
+def share_instance_update_access_status(context, share_instance_id, status):
462
+    """Update access rules status of share instance."""
463
+    return IMPL.share_instance_update_access_status(context, share_instance_id,
464
+                                                    status)
458 465
 
459 466
 
460 467
 ####################

+ 133
- 0
manila/db/migrations/alembic/versions/344c1ac4747f_add_share_instance_access_rules_status.py View File

@@ -0,0 +1,133 @@
1
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+# not use this file except in compliance with the License. You may obtain
3
+# a copy of the License at
4
+#
5
+# http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+# Unless required by applicable law or agreed to in writing, software
8
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+# License for the specific language governing permissions and limitations
11
+# under the License.
12
+
13
+"""Remove access rules status and add access_rule_status to share_instance
14
+model
15
+
16
+Revision ID: 344c1ac4747f
17
+Revises: dda6de06349
18
+Create Date: 2015-11-18 14:58:55.806396
19
+
20
+"""
21
+
22
+# revision identifiers, used by Alembic.
23
+revision = '344c1ac4747f'
24
+down_revision = 'dda6de06349'
25
+
26
+from alembic import op
27
+from sqlalchemy import Column, String
28
+
29
+from manila.common import constants
30
+from manila.db.migrations import utils
31
+
32
+
33
+priorities = {
34
+    'active': 0,
35
+    'new': 1,
36
+    'error': 2
37
+}
38
+
39
+upgrade_data_mapping = {
40
+    'active': 'active',
41
+    'new': 'out_of_sync',
42
+    'error': 'error',
43
+}
44
+
45
+downgrade_data_mapping = {
46
+    'active': 'active',
47
+    # NOTE(u_glide): We cannot determine is it applied rule or not in Manila,
48
+    # so administrator should manually handle such access rules.
49
+    'out_of_sync': 'error',
50
+    'error': 'error',
51
+}
52
+
53
+
54
+def upgrade():
55
+    """Transform individual access rules states to 'access_rules_status'.
56
+
57
+    WARNING: This method performs lossy converting of existing data in DB.
58
+    """
59
+    op.add_column(
60
+        'share_instances',
61
+        Column('access_rules_status', String(length=255))
62
+    )
63
+
64
+    connection = op.get_bind()
65
+    share_instances_table = utils.load_table('share_instances', connection)
66
+    instance_access_table = utils.load_table('share_instance_access_map',
67
+                                             connection)
68
+
69
+    # NOTE(u_glide): Data migrations shouldn't be performed on live clouds
70
+    # because it will lead to unpredictable behaviour of running operations
71
+    # like migration.
72
+    instances_query = (
73
+        share_instances_table.select()
74
+        .where(share_instances_table.c.status == constants.STATUS_AVAILABLE)
75
+        .where(share_instances_table.c.deleted == 'False')
76
+    )
77
+
78
+    for instance in connection.execute(instances_query):
79
+
80
+        access_mappings_query = instance_access_table.select().where(
81
+            instance_access_table.c.share_instance_id == instance['id']
82
+        ).where(instance_access_table.c.deleted == 'False')
83
+
84
+        status = constants.STATUS_ACTIVE
85
+
86
+        for access_rule in connection.execute(access_mappings_query):
87
+
88
+            if (access_rule['state'] == constants.STATUS_DELETING or
89
+                    access_rule['state'] not in priorities):
90
+                continue
91
+
92
+            if priorities[access_rule['state']] > priorities[status]:
93
+                status = access_rule['state']
94
+
95
+        op.execute(
96
+            share_instances_table.update().where(
97
+                share_instances_table.c.id == instance['id']
98
+            ).values({'access_rules_status': upgrade_data_mapping[status]})
99
+        )
100
+
101
+    op.drop_column('share_instance_access_map', 'state')
102
+
103
+
104
+def downgrade():
105
+    op.add_column(
106
+        'share_instance_access_map',
107
+        Column('state', String(length=255))
108
+    )
109
+
110
+    connection = op.get_bind()
111
+    share_instances_table = utils.load_table('share_instances', connection)
112
+    instance_access_table = utils.load_table('share_instance_access_map',
113
+                                             connection)
114
+
115
+    instances_query = (
116
+        share_instances_table.select()
117
+        .where(share_instances_table.c.status == constants.STATUS_AVAILABLE)
118
+        .where(share_instances_table.c.deleted == 'False')
119
+    )
120
+
121
+    for instance in connection.execute(instances_query):
122
+
123
+        state = downgrade_data_mapping[instance['access_rules_status']]
124
+
125
+        op.execute(
126
+            instance_access_table.update().where(
127
+                instance_access_table.c.share_instance_id == instance['id']
128
+            ).where(instance_access_table.c.deleted == 'False').values(
129
+                {'state': state}
130
+            )
131
+        )
132
+
133
+    op.drop_column('share_instances', 'access_rules_status')

+ 25
- 16
manila/db/sqlalchemy/api.py View File

@@ -1538,9 +1538,12 @@ def _share_access_get_query(context, session, values, read_deleted='no'):
1538 1538
     return query.filter_by(**values)
1539 1539
 
1540 1540
 
1541
-def _share_instance_access_query(context, session, access_id,
1541
+def _share_instance_access_query(context, session, access_id=None,
1542 1542
                                  instance_id=None):
1543
-    filters = {'access_id': access_id}
1543
+    filters = {}
1544
+
1545
+    if access_id is not None:
1546
+        filters.update({'access_id': access_id})
1544 1547
 
1545 1548
     if instance_id is not None:
1546 1549
         filters.update({'share_instance_id': instance_id})
@@ -1555,7 +1558,6 @@ def share_access_create(context, values):
1555 1558
     session = get_session()
1556 1559
     with session.begin():
1557 1560
         access_ref = models.ShareAccessMapping()
1558
-        state = values.pop('state', None)
1559 1561
         access_ref.update(values)
1560 1562
         access_ref.save(session=session)
1561 1563
 
@@ -1566,8 +1568,6 @@ def share_access_create(context, values):
1566 1568
                 'share_instance_id': instance['id'],
1567 1569
                 'access_id': access_ref['id'],
1568 1570
             }
1569
-            if state is not None:
1570
-                vals.update({'state': state})
1571 1571
 
1572 1572
             _share_instance_access_create(vals, session)
1573 1573
 
@@ -1614,6 +1614,18 @@ def share_access_get_all_for_share(context, share_id):
1614 1614
                                    {'share_id': share_id}).all()
1615 1615
 
1616 1616
 
1617
+@require_context
1618
+def share_access_get_all_for_instance(context, instance_id, session=None):
1619
+    """Get all access rules related to a certain share instance."""
1620
+    session = get_session()
1621
+    return _share_access_get_query(context, session, {}).join(
1622
+        models.ShareInstanceAccessMapping,
1623
+        models.ShareInstanceAccessMapping.access_id ==
1624
+        models.ShareAccessMapping.id).filter(
1625
+        models.ShareInstanceAccessMapping.share_instance_id ==
1626
+        instance_id).all()
1627
+
1628
+
1617 1629
 @require_context
1618 1630
 def share_instance_access_get_all(context, access_id, session=None):
1619 1631
     if not session:
@@ -1644,8 +1656,7 @@ def share_access_delete(context, access_id):
1644 1656
             raise exception.InvalidShareAccess(msg)
1645 1657
 
1646 1658
         session.query(models.ShareAccessMapping).\
1647
-            filter_by(id=access_id).soft_delete(update_status=True,
1648
-                                                status_field_name='state')
1659
+            filter_by(id=access_id).soft_delete()
1649 1660
 
1650 1661
 
1651 1662
 @require_context
@@ -1653,8 +1664,7 @@ def share_access_delete_all_by_share(context, share_id):
1653 1664
     session = get_session()
1654 1665
     with session.begin():
1655 1666
         session.query(models.ShareAccessMapping). \
1656
-            filter_by(share_id=share_id).soft_delete(update_status=True,
1657
-                                                     status_field_name='state')
1667
+            filter_by(share_id=share_id).soft_delete()
1658 1668
 
1659 1669
 
1660 1670
 @require_context
@@ -1668,8 +1678,7 @@ def share_instance_access_delete(context, mapping_id):
1668 1678
         if not mapping:
1669 1679
             exception.NotFound()
1670 1680
 
1671
-        mapping.soft_delete(session, update_status=True,
1672
-                            status_field_name='state')
1681
+        mapping.soft_delete(session)
1673 1682
 
1674 1683
         other_mappings = share_instance_access_get_all(
1675 1684
             context, mapping['access_id'], session)
@@ -1679,18 +1688,18 @@ def share_instance_access_delete(context, mapping_id):
1679 1688
             (
1680 1689
                 session.query(models.ShareAccessMapping)
1681 1690
                 .filter_by(id=mapping['access_id'])
1682
-                .soft_delete(update_status=True, status_field_name='state')
1691
+                .soft_delete()
1683 1692
             )
1684 1693
 
1685 1694
 
1686 1695
 @require_context
1687 1696
 @oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
1688
-def share_instance_access_update_state(context, mapping_id, state):
1697
+def share_instance_update_access_status(context, share_instance_id, status):
1689 1698
     session = get_session()
1690 1699
     with session.begin():
1691
-        mapping = session.query(models.ShareInstanceAccessMapping).\
1692
-            filter_by(id=mapping_id).first()
1693
-        mapping.update({'state': state})
1700
+        mapping = session.query(models.ShareInstance).\
1701
+            filter_by(id=share_instance_id).first()
1702
+        mapping.update({'access_rules_status': status})
1694 1703
         mapping.save(session=session)
1695 1704
         return mapping
1696 1705
 

+ 62
- 39
manila/db/sqlalchemy/models.py View File

@@ -189,7 +189,7 @@ class Share(BASE, ManilaBase):
189 189
     __tablename__ = 'shares'
190 190
     _extra_keys = ['name', 'export_location', 'export_locations', 'status',
191 191
                    'host', 'share_server_id', 'share_network_id',
192
-                   'availability_zone']
192
+                   'availability_zone', 'access_rules_status']
193 193
 
194 194
     @property
195 195
     def name(self):
@@ -253,6 +253,10 @@ class Share(BASE, ManilaBase):
253 253
                     result = self.instances[0]
254 254
         return result
255 255
 
256
+    @property
257
+    def access_rules_status(self):
258
+        return get_access_rules_status(self.instances)
259
+
256 260
     id = Column(String(36), primary_key=True)
257 261
     deleted = Column(String(36), default='False')
258 262
     user_id = Column(String(255))
@@ -326,6 +330,18 @@ class ShareInstance(BASE, ManilaBase):
326 330
     deleted = Column(String(36), default='False')
327 331
     host = Column(String(255))
328 332
     status = Column(String(255))
333
+
334
+    ACCESS_STATUS_PRIORITIES = {
335
+        constants.STATUS_ACTIVE: 0,
336
+        constants.STATUS_OUT_OF_SYNC: 1,
337
+        constants.STATUS_ERROR: 2,
338
+    }
339
+
340
+    access_rules_status = Column(Enum(constants.STATUS_ACTIVE,
341
+                                      constants.STATUS_OUT_OF_SYNC,
342
+                                      constants.STATUS_ERROR),
343
+                                 default=constants.STATUS_ACTIVE)
344
+
329 345
     scheduled_at = Column(DateTime)
330 346
     launched_at = Column(DateTime)
331 347
     terminated_at = Column(DateTime)
@@ -476,26 +492,6 @@ class ShareAccessMapping(BASE, ManilaBase):
476 492
     """Represents access to share."""
477 493
     __tablename__ = 'share_access_map'
478 494
 
479
-    @property
480
-    def state(self):
481
-        state = ShareInstanceAccessMapping.STATE_NEW
482
-
483
-        if len(self.instance_mappings) > 0:
484
-            state = ShareInstanceAccessMapping.STATE_ACTIVE
485
-            priorities = ShareInstanceAccessMapping.STATE_PRIORITIES
486
-
487
-            for mapping in self.instance_mappings:
488
-                priority = priorities.get(
489
-                    mapping['state'], ShareInstanceAccessMapping.STATE_ERROR)
490
-
491
-                if priority > priorities.get(state):
492
-                    state = mapping['state']
493
-
494
-                if state == ShareInstanceAccessMapping.STATE_ERROR:
495
-                    break
496
-
497
-        return state
498
-
499 495
     id = Column(String(36), primary_key=True)
500 496
     deleted = Column(String(36), default='False')
501 497
     share_id = Column(String(36), ForeignKey('shares.id'))
@@ -516,33 +512,36 @@ class ShareAccessMapping(BASE, ManilaBase):
516 512
         )
517 513
     )
518 514
 
515
+    @property
516
+    def state(self):
517
+        instances = [im.instance for im in self.instance_mappings]
518
+        access_rules_status = get_access_rules_status(instances)
519
+
520
+        if access_rules_status == constants.STATUS_OUT_OF_SYNC:
521
+            return constants.STATUS_NEW
522
+        else:
523
+            return access_rules_status
524
+
519 525
 
520 526
 class ShareInstanceAccessMapping(BASE, ManilaBase):
521 527
     """Represents access to individual share instances."""
522
-    STATE_NEW = constants.STATUS_NEW
523
-    STATE_ACTIVE = constants.STATUS_ACTIVE
524
-    STATE_DELETING = constants.STATUS_DELETING
525
-    STATE_DELETED = constants.STATUS_DELETED
526
-    STATE_ERROR = constants.STATUS_ERROR
527
-
528
-    # NOTE(u_glide): State with greatest priority becomes a state of access
529
-    # rule
530
-    STATE_PRIORITIES = {
531
-        STATE_ACTIVE: 0,
532
-        STATE_NEW: 1,
533
-        STATE_DELETED: 2,
534
-        STATE_DELETING: 3,
535
-        STATE_ERROR: 4
536
-    }
537 528
 
538 529
     __tablename__ = 'share_instance_access_map'
539 530
     id = Column(String(36), primary_key=True)
540 531
     deleted = Column(String(36), default='False')
541 532
     share_instance_id = Column(String(36), ForeignKey('share_instances.id'))
542 533
     access_id = Column(String(36), ForeignKey('share_access_map.id'))
543
-    state = Column(Enum(STATE_NEW, STATE_ACTIVE,
544
-                        STATE_DELETING, STATE_DELETED, STATE_ERROR),
545
-                   default=STATE_NEW)
534
+
535
+    instance = orm.relationship(
536
+        "ShareInstance",
537
+        lazy='immediate',
538
+        primaryjoin=(
539
+            'and_('
540
+            'ShareInstanceAccessMapping.share_instance_id == '
541
+            'ShareInstance.id, '
542
+            'ShareInstanceAccessMapping.deleted == "False")'
543
+        )
544
+    )
546 545
 
547 546
 
548 547
 class ShareSnapshot(BASE, ManilaBase):
@@ -902,3 +901,27 @@ def register_models():
902 901
     engine = create_engine(CONF.database.connection, echo=False)
903 902
     for model in models:
904 903
         model.metadata.create_all(engine)
904
+
905
+
906
+def get_access_rules_status(instances):
907
+    share_access_status = constants.STATUS_ACTIVE
908
+
909
+    if len(instances) == 0:
910
+        return share_access_status
911
+
912
+    priorities = ShareInstance.ACCESS_STATUS_PRIORITIES
913
+
914
+    for instance in instances:
915
+        if instance['status'] != constants.STATUS_AVAILABLE:
916
+            continue
917
+
918
+        instance_access_status = instance['access_rules_status']
919
+
920
+        if priorities.get(instance_access_status) > priorities.get(
921
+                share_access_status):
922
+            share_access_status = instance_access_status
923
+
924
+        if share_access_status == constants.STATUS_ERROR:
925
+            break
926
+
927
+    return share_access_status

+ 1
- 1
manila/exception.py View File

@@ -411,7 +411,7 @@ class ShareAccessExists(ManilaException):
411 411
 
412 412
 
413 413
 class InvalidShareAccess(Invalid):
414
-    message = _("Invalid access_rule: %(reason)s.")
414
+    message = _("Invalid access rule: %(reason)s")
415 415
 
416 416
 
417 417
 class InvalidShareAccessLevel(Invalid):

+ 155
- 0
manila/share/access.py View File

@@ -0,0 +1,155 @@
1
+# Copyright (c) 2015 Mirantis Inc.
2
+# All Rights Reserved.
3
+#
4
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+#    not use this file except in compliance with the License. You may obtain
6
+#    a copy of the License at
7
+#
8
+#         http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+#    Unless required by applicable law or agreed to in writing, software
11
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+#    License for the specific language governing permissions and limitations
14
+#    under the License.
15
+
16
+from oslo_log import log
17
+import six
18
+
19
+from manila.common import constants
20
+from manila import exception
21
+from manila.i18n import _LI
22
+
23
+LOG = log.getLogger(__name__)
24
+
25
+
26
+class ShareInstanceAccess(object):
27
+
28
+    def __init__(self, db, driver):
29
+        self.db = db
30
+        self.driver = driver
31
+
32
+    def update_access_rules(self, context, share_instance_id, add_rules=None,
33
+                            delete_rules=None, share_server=None):
34
+        """Update access rules in driver and database for given share instance.
35
+
36
+        :param context: current context
37
+        :param share_instance_id: Id of the share instance model
38
+        :param add_rules: list with ShareAccessMapping models or None - rules
39
+        which should be added
40
+        :param delete_rules: list with ShareAccessMapping models, "all", None
41
+        - rules which should be deleted. If "all" is provided - all rules will
42
+        be deleted.
43
+        :param share_server: Share server model or None
44
+        """
45
+        share_instance = self.db.share_instance_get(
46
+            context, share_instance_id, with_share_data=True)
47
+
48
+        add_rules = add_rules or []
49
+        delete_rules = delete_rules or []
50
+        remove_rules = None
51
+
52
+        if six.text_type(delete_rules).lower() == "all":
53
+            # NOTE(ganso): if we are deleting an instance or clearing all
54
+            # the rules, we want to remove only the ones related
55
+            # to this instance.
56
+            delete_rules = self.db.share_access_get_all_for_instance(
57
+                context, share_instance['id'])
58
+            rules = []
59
+        else:
60
+            rules = self.db.share_access_get_all_for_share(
61
+                context, share_instance['share_id'])
62
+            if delete_rules:
63
+                delete_ids = [rule['id'] for rule in delete_rules]
64
+                rules = list(filter(lambda r: r['id'] not in delete_ids,
65
+                                    rules))
66
+                # NOTE(ganso): trigger maintenance mode
67
+                if share_instance['access_rules_status'] == (
68
+                        constants.STATUS_ERROR):
69
+                    remove_rules = delete_rules
70
+                    delete_rules = []
71
+
72
+        try:
73
+            try:
74
+                self.driver.update_access(
75
+                    context,
76
+                    share_instance,
77
+                    rules,
78
+                    add_rules=add_rules,
79
+                    delete_rules=delete_rules,
80
+                    share_server=share_server
81
+                )
82
+            except NotImplementedError:
83
+                # NOTE(u_glide): Fallback to legacy allow_access/deny_access
84
+                # for drivers without update_access() method support
85
+
86
+                self._update_access_fallback(add_rules, context, delete_rules,
87
+                                             remove_rules, share_instance,
88
+                                             share_server)
89
+        except Exception as e:
90
+            error = six.text_type(e)
91
+            LOG.exception(error)
92
+            self.db.share_instance_update_access_status(
93
+                context,
94
+                share_instance['id'],
95
+                constants.STATUS_ERROR)
96
+            raise exception.ManilaException(message=error)
97
+
98
+        # NOTE(ganso): remove rules after maintenance is complete
99
+        if remove_rules:
100
+            delete_rules = remove_rules
101
+
102
+        self._remove_access_rules(context, delete_rules, share_instance['id'])
103
+
104
+        self.db.share_instance_update_access_status(
105
+            context,
106
+            share_instance['id'],
107
+            constants.STATUS_ACTIVE
108
+        )
109
+
110
+        LOG.info(_LI("Access rules were successfully applied for "
111
+                     "share instance: %s"),
112
+                 share_instance['id'])
113
+
114
+    def _update_access_fallback(self, add_rules, context, delete_rules,
115
+                                remove_rules, share_instance, share_server):
116
+        for rule in add_rules:
117
+            LOG.info(
118
+                _LI("Applying access rule '%(rule)s' for share "
119
+                    "instance '%(instance)s'"),
120
+                {'rule': rule['id'], 'instance': share_instance['id']}
121
+            )
122
+
123
+            self.driver.allow_access(
124
+                context,
125
+                share_instance,
126
+                rule,
127
+                share_server=share_server
128
+            )
129
+
130
+        # NOTE(ganso): Fallback mode temporary compatibility workaround
131
+        if remove_rules:
132
+            delete_rules = remove_rules
133
+        for rule in delete_rules:
134
+            LOG.info(
135
+                _LI("Denying access rule '%(rule)s' from share "
136
+                    "instance '%(instance)s'"),
137
+                {'rule': rule['id'], 'instance': share_instance['id']}
138
+            )
139
+
140
+            self.driver.deny_access(
141
+                context,
142
+                share_instance,
143
+                rule,
144
+                share_server=share_server
145
+            )
146
+
147
+    def _remove_access_rules(self, context, access_rules, share_instance_id):
148
+        if not access_rules:
149
+            return
150
+
151
+        for rule in access_rules:
152
+            access_mapping = self.db.share_instance_access_get(
153
+                context, rule['id'], share_instance_id)
154
+
155
+            self.db.share_instance_access_delete(context, access_mapping['id'])

+ 40
- 30
manila/share/api.py View File

@@ -818,11 +818,14 @@ class API(base.Base):
818 818
             'access_to': access_to,
819 819
             'access_level': access_level,
820 820
         }
821
-        for access in self.db.share_access_get_all_by_type_and_access(
822
-                ctx, share['id'], access_type, access_to):
823
-            if access['state'] != constants.STATUS_ERROR:
821
+
822
+        share_access_list = self.db.share_access_get_all_by_type_and_access(
823
+            ctx, share['id'], access_type, access_to)
824
+
825
+        if len(share_access_list) > 0:
824 826
                 raise exception.ShareAccessExists(access_type=access_type,
825 827
                                                   access=access_to)
828
+
826 829
         if access_level not in constants.ACCESS_LEVELS + (None, ):
827 830
             msg = _("Invalid share access level: %s.") % access_level
828 831
             raise exception.InvalidShareAccess(reason=msg)
@@ -830,6 +833,10 @@ class API(base.Base):
830 833
 
831 834
         for share_instance in share.instances:
832 835
             self.allow_access_to_instance(ctx, share_instance, access)
836
+
837
+        # NOTE(tpsilva): refreshing share_access model
838
+        access = self.db.share_access_get(ctx, access['id'])
839
+
833 840
         return {
834 841
             'id': access['id'],
835 842
             'share_id': access['share_id'],
@@ -846,6 +853,22 @@ class API(base.Base):
846 853
             msg = _("Invalid share instance host: %s") % share_instance['host']
847 854
             raise exception.InvalidShareInstance(reason=msg)
848 855
 
856
+        if share_instance['access_rules_status'] != constants.STATUS_ACTIVE:
857
+            status = share_instance['access_rules_status']
858
+            msg = _("Share instance should have '%(valid_status)s' "
859
+                    "access rules status, but current status is: "
860
+                    "%(status)s.") % {
861
+                'valid_status': constants.STATUS_ACTIVE,
862
+                'status': status,
863
+            }
864
+
865
+            raise exception.InvalidShareInstance(reason=msg)
866
+
867
+        self.db.share_instance_update_access_status(
868
+            context, share_instance['id'],
869
+            constants.STATUS_OUT_OF_SYNC
870
+        )
871
+
849 872
         self.share_rpcapi.allow_access(context, share_instance, access)
850 873
 
851 874
     def deny_access(self, ctx, share, access):
@@ -860,27 +883,14 @@ class API(base.Base):
860 883
             msg = _("Share status must be %s") % constants.STATUS_AVAILABLE
861 884
             raise exception.InvalidShare(reason=msg)
862 885
 
863
-        # Then check state of the access rule
864
-        if (access['state'] == constants.STATUS_ERROR and not
865
-                self.db.share_instance_access_get_all(ctx, access['id'])):
866
-            self.db.share_access_delete(ctx, access["id"])
867
-
868
-        elif access['state'] in [constants.STATUS_ACTIVE,
869
-                                 constants.STATUS_ERROR]:
870
-            for share_instance in share.instances:
871
-                try:
872
-                    self.deny_access_to_instance(ctx, share_instance, access)
873
-                except exception.NotFound:
874
-                    LOG.warning(_LW("Access rule %(access_id)s not found "
875
-                                    "for instance %(instance_id)s.") % {
876
-                                'access_id': access['id'],
877
-                                'instance_id': share_instance['id']})
878
-        else:
879
-            msg = _("Access policy should be %(active)s or in %(error)s "
880
-                    "state") % {"active": constants.STATUS_ACTIVE,
881
-                                "error": constants.STATUS_ERROR}
882
-            raise exception.InvalidShareAccess(reason=msg)
883
-            # update share state and send message to manager
886
+        for share_instance in share.instances:
887
+            try:
888
+                self.deny_access_to_instance(ctx, share_instance, access)
889
+            except exception.NotFound:
890
+                LOG.warning(_LW("Access rule %(access_id)s not found "
891
+                                "for instance %(instance_id)s.") % {
892
+                    'access_id': access['id'],
893
+                    'instance_id': share_instance['id']})
884 894
 
885 895
     def deny_access_to_instance(self, context, share_instance, access):
886 896
         policy.check_policy(context, 'share', 'deny_access')
@@ -889,11 +899,10 @@ class API(base.Base):
889 899
             msg = _("Invalid share instance host: %s") % share_instance['host']
890 900
             raise exception.InvalidShareInstance(reason=msg)
891 901
 
892
-        access_mapping = self.db.share_instance_access_get(
893
-            context, access['id'], share_instance['id'])
894
-        self.db.share_instance_access_update_state(
895
-            context, access_mapping['id'],
896
-            access_mapping.STATE_DELETING)
902
+        if share_instance['access_rules_status'] != constants.STATUS_ERROR:
903
+            self.db.share_instance_update_access_status(
904
+                context, share_instance['id'],
905
+                constants.STATUS_OUT_OF_SYNC)
897 906
 
898 907
         self.share_rpcapi.deny_access(context, share_instance, access)
899 908
 
@@ -905,7 +914,8 @@ class API(base.Base):
905 914
                  'access_type': rule.access_type,
906 915
                  'access_to': rule.access_to,
907 916
                  'access_level': rule.access_level,
908
-                 'state': rule.state} for rule in rules]
917
+                 'state': rule.state,
918
+                 } for rule in rules]
909 919
 
910 920
     def access_get(self, context, access_id):
911 921
         """Returns access rule with the id."""

+ 26
- 0
manila/share/driver.py View File

@@ -603,6 +603,32 @@ class ShareDriver(object):
603 603
         """Deny access to the share."""
604 604
         raise NotImplementedError()
605 605
 
606
+    def update_access(self, context, share, access_rules, add_rules=None,
607
+                      delete_rules=None, share_server=None):
608
+        """Update access rules for given share.
609
+
610
+        Drivers should support 2 different cases in this method:
611
+        1. Recovery after error - 'access_rules' contains all access_rules,
612
+        'add_rules' and 'delete_rules' are None. Driver should clear any
613
+        existent access rules and apply all access rules for given share.
614
+        This recovery is made at driver start up.
615
+
616
+        2. Adding/Deleting of several access rules - 'access_rules' contains
617
+        all access_rules, 'add_rules' and 'delete_rules' contain rules which
618
+        should be added/deleted. Driver can ignore rules in 'access_rules' and
619
+        apply only rules from 'add_rules' and 'delete_rules'.
620
+
621
+        :param context: Current context
622
+        :param share: Share model with share data.
623
+        :param access_rules: All access rules for given share
624
+        :param add_rules: None or List of access rules which should be added
625
+               access_rules already contains these rules.
626
+        :param delete_rules: None or List of access rules which should be
627
+               removed. access_rules doesn't contain these rules.
628
+        :param share_server: None or Share server model
629
+        """
630
+        raise NotImplementedError()
631
+
606 632
     def check_for_setup_error(self):
607 633
         """Check for setup error."""
608 634
         max_ratio = self.configuration.safe_get('max_over_subscription_ratio')

+ 46
- 79
manila/share/manager.py View File

@@ -41,6 +41,7 @@ from manila.i18n import _LI
41 41
 from manila.i18n import _LW
42 42
 from manila import manager
43 43
 from manila import quota
44
+from manila.share import access
44 45
 import manila.share.configuration
45 46
 from manila.share import drivers_private_data
46 47
 from manila.share import migration
@@ -136,7 +137,7 @@ def add_hooks(f):
136 137
 class ShareManager(manager.SchedulerDependentManager):
137 138
     """Manages NAS storages."""
138 139
 
139
-    RPC_API_VERSION = '1.6'
140
+    RPC_API_VERSION = '1.7'
140 141
 
141 142
     def __init__(self, share_driver=None, service_name=None, *args, **kwargs):
142 143
         """Load the driver from args, or from flags."""
@@ -167,6 +168,8 @@ class ShareManager(manager.SchedulerDependentManager):
167 168
             configuration=self.configuration
168 169
         )
169 170
 
171
+        self.access_helper = access.ShareInstanceAccess(self.db, self.driver)
172
+
170 173
         self.hooks = []
171 174
         self._init_hook_drivers()
172 175
 
@@ -271,28 +274,18 @@ class ShareManager(manager.SchedulerDependentManager):
271 274
                 self.db.share_export_locations_update(
272 275
                     ctxt, share_instance['id'], export_locations)
273 276
 
274
-            rules = self.db.share_access_get_all_for_share(
275
-                ctxt, share_instance['share_id'])
276
-            for access_ref in rules:
277
-                if access_ref['state'] != constants.STATUS_ACTIVE:
278
-                    continue
277
+            if share_instance['access_rules_status'] == (
278
+                    constants.STATUS_OUT_OF_SYNC):
279 279
 
280 280
                 try:
281
-                    self.driver.allow_access(ctxt, share_instance, access_ref,
282
-                                             share_server=share_server)
283
-                except exception.ShareAccessExists:
284
-                    pass
281
+                    self.access_helper.update_access_rules(
282
+                        ctxt, share_instance['id'], share_server=share_server)
285 283
                 except Exception as e:
286 284
                     LOG.error(
287
-                        _LE("Unexpected exception during share access"
288
-                            " allow operation. Share id is '%(s_id)s'"
289
-                            ", access rule type is '%(ar_type)s', "
290
-                            "access rule id is '%(ar_id)s', exception"
291
-                            " is '%(e)s'."),
292
-                        {'s_id': share_instance['id'],
293
-                         'ar_type': access_ref['access_type'],
294
-                         'ar_id': access_ref['id'],
295
-                         'e': six.text_type(e)},
285
+                        _LE("Unexpected error occurred while updating access "
286
+                            "rules for share instance %(s_id)s. "
287
+                            "Exception: \n%(e)s."),
288
+                        {'s_id': share_instance['id'], 'e': six.text_type(e)},
296 289
                     )
297 290
 
298 291
         self.publish_service_capabilities(ctxt)
@@ -946,8 +939,12 @@ class ShareManager(manager.SchedulerDependentManager):
946 939
 
947 940
         if self.configuration.safe_get('unmanage_remove_access_rules'):
948 941
             try:
949
-                self._remove_share_access_rules(context, share_ref,
950
-                                                share_instance, share_server)
942
+                self.access_helper.update_access_rules(
943
+                    context,
944
+                    share_instance['id'],
945
+                    delete_rules="all",
946
+                    share_server=share_server
947
+                )
951 948
             except Exception as e:
952 949
                 share_manage_set_error_status(
953 950
                     _LE("Can not remove access rules of share: %s."), e)
@@ -962,12 +959,15 @@ class ShareManager(manager.SchedulerDependentManager):
962 959
         """Delete a share instance."""
963 960
         context = context.elevated()
964 961
         share_instance = self._get_share_instance(context, share_instance_id)
965
-        share = self.db.share_get(context, share_instance['share_id'])
966 962
         share_server = self._get_share_server(context, share_instance)
967 963
 
968 964
         try:
969
-            self._remove_share_access_rules(context, share, share_instance,
970
-                                            share_server)
965
+            self.access_helper.update_access_rules(
966
+                context,
967
+                share_instance_id,
968
+                delete_rules="all",
969
+                share_server=share_server
970
+            )
971 971
             self.driver.delete_share(context, share_instance,
972 972
                                      share_server=share_server)
973 973
         except Exception:
@@ -1004,15 +1004,6 @@ class ShareManager(manager.SchedulerDependentManager):
1004 1004
         for server in servers:
1005 1005
             self.delete_share_server(ctxt, server)
1006 1006
 
1007
-    def _remove_share_access_rules(self, context, share_ref, share_instance,
1008
-                                   share_server):
1009
-        rules = self.db.share_access_get_all_for_share(
1010
-            context, share_ref['id'])
1011
-
1012
-        for access_ref in rules:
1013
-            self._deny_access(context, access_ref,
1014
-                              share_instance, share_server)
1015
-
1016 1007
     @add_hooks
1017 1008
     @utils.require_driver_initialized
1018 1009
     def create_snapshot(self, context, share_id, snapshot_id):
@@ -1097,61 +1088,37 @@ class ShareManager(manager.SchedulerDependentManager):
1097 1088
 
1098 1089
     @add_hooks
1099 1090
     @utils.require_driver_initialized
1100
-    def allow_access(self, context, share_instance_id, access_id):
1091
+    def allow_access(self, context, share_instance_id, access_rules):
1101 1092
         """Allow access to some share instance."""
1102
-        access_mapping = self.db.share_instance_access_get(context, access_id,
1103
-                                                           share_instance_id)
1093
+        add_rules = [self.db.share_access_get(context, rule_id)
1094
+                     for rule_id in access_rules]
1104 1095
 
1105
-        if access_mapping['state'] != access_mapping.STATE_NEW:
1106
-            return
1107
-
1108
-        try:
1109
-            access_ref = self.db.share_access_get(context, access_id)
1110
-            share_instance = self.db.share_instance_get(
1111
-                context, share_instance_id, with_share_data=True)
1112
-            share_server = self._get_share_server(context, share_instance)
1113
-            self.driver.allow_access(context, share_instance, access_ref,
1114
-                                     share_server=share_server)
1115
-            self.db.share_instance_access_update_state(
1116
-                context, access_mapping['id'], access_mapping.STATE_ACTIVE)
1117
-        except Exception:
1118
-            with excutils.save_and_reraise_exception():
1119
-                self.db.share_instance_access_update_state(
1120
-                    context, access_mapping['id'], access_mapping.STATE_ERROR)
1096
+        share_instance = self._get_share_instance(context, share_instance_id)
1097
+        share_server = self._get_share_server(context, share_instance)
1121 1098
 
1122
-        LOG.info(_LI("'%(access_to)s' has been successfully allowed "
1123
-                     "'%(access_level)s' access on share instance "
1124
-                     "%(share_instance_id)s."),
1125
-                 {'access_to': access_ref['access_to'],
1126
-                  'access_level': access_ref['access_level'],
1127
-                  'share_instance_id': share_instance_id})
1099
+        return self.access_helper.update_access_rules(
1100
+            context,
1101
+            share_instance_id,
1102
+            add_rules=add_rules,
1103
+            share_server=share_server
1104
+        )
1128 1105
 
1129 1106
     @add_hooks
1130 1107
     @utils.require_driver_initialized
1131
-    def deny_access(self, context, share_instance_id, access_id):
1108
+    def deny_access(self, context, share_instance_id, access_rules):
1132 1109
         """Deny access to some share."""
1133
-        access_ref = self.db.share_access_get(context, access_id)
1134
-        share_instance = self.db.share_instance_get(
1135
-            context, share_instance_id, with_share_data=True)
1136
-        share_server = self._get_share_server(context, share_instance)
1137
-        self._deny_access(context, access_ref, share_instance, share_server)
1110
+        delete_rules = [self.db.share_access_get(context, rule_id)
1111
+                        for rule_id in access_rules]
1138 1112
 
1139
-        LOG.info(_LI("'(access_to)s' has been successfully denied access to "
1140
-                     "share instance %(share_instance_id)s."),
1141
-                 {'access_to': access_ref['access_to'],
1142
-                  'share_instance_id': share_instance_id})
1113
+        share_instance = self._get_share_instance(context, share_instance_id)
1114
+        share_server = self._get_share_server(context, share_instance)
1143 1115
 
1144
-    def _deny_access(self, context, access_ref, share_instance, share_server):
1145
-        access_mapping = self.db.share_instance_access_get(
1146
-            context, access_ref['id'], share_instance['id'])
1147
-        try:
1148
-            self.driver.deny_access(context, share_instance, access_ref,
1149
-                                    share_server=share_server)
1150
-            self.db.share_instance_access_delete(context, access_mapping['id'])
1151
-        except Exception:
1152
-            with excutils.save_and_reraise_exception():
1153
-                self.db.share_instance_access_update_state(
1154
-                    context, access_mapping['id'], access_mapping.STATE_ERROR)
1116
+        return self.access_helper.update_access_rules(
1117
+            context,
1118
+            share_instance_id,
1119
+            delete_rules=delete_rules,
1120
+            share_server=share_server
1121
+        )
1155 1122
 
1156 1123
     @periodic_task.periodic_task(spacing=CONF.periodic_interval)
1157 1124
     @utils.require_driver_initialized

+ 29
- 92
manila/share/migration.py View File

@@ -104,115 +104,52 @@ class ShareMigrationHelper(object):
104 104
     def deny_rules_and_wait(self, context, share, saved_rules):
105 105
 
106 106
         api = share_api.API()
107
+        api.deny_access_to_instance(context, share.instance, saved_rules)
107 108
 
108
-        # Deny each one.
109
-        for instance in share.instances:
110
-            for access in saved_rules:
111
-                api.deny_access_to_instance(context, instance, access)
112
-
113
-        # Wait for all rules to be cleared.
114
-        starttime = time.time()
115
-        deadline = starttime + self.migration_wait_access_rules_timeout
116
-        tries = 0
117
-        rules = self.db.share_access_get_all_for_share(context, share['id'])
118
-        while len(rules) > 0:
119
-            tries += 1
120
-            now = time.time()
121
-            if now > deadline:
122
-                msg = _("Timeout removing access rules from share "
123
-                        "%(share_id)s. Timeout was %(timeout)s seconds.") % {
124
-                    'share_id': share['id'],
125
-                    'timeout': self.migration_wait_access_rules_timeout}
126
-                raise exception.ShareMigrationFailed(reason=msg)
127
-            else:
128
-                time.sleep(tries ** 2)
129
-            rules = self.db.share_access_get_all_for_share(
130
-                context, share['id'])
109
+        self.wait_for_access_update(share.instance)
131 110
 
132 111
     def add_rules_and_wait(self, context, share, saved_rules,
133 112
                            access_level=None):
134
-
113
+        rules = []
135 114
         for access in saved_rules:
136
-            if access_level:
137
-                level = access_level
138
-            else:
139
-                level = access['access_level']
140
-            self.api.allow_access(context, share, access['access_type'],
141
-                                  access['access_to'], level)
142
-
115
+            values = {
116
+                'share_id': share['id'],
117
+                'access_type': access['access_type'],
118
+                'access_level': access_level or access['access_level'],
119
+                'access_to': access['access_to'],
120
+            }
121
+            rules.append(self.db.share_access_create(context, values))
122
+
123
+        self.api.allow_access_to_instance(context, share.instance, rules)
124
+        self.wait_for_access_update(share.instance)
125
+
126
+    def wait_for_access_update(self, share_instance):
143 127
         starttime = time.time()
144 128
         deadline = starttime + self.migration_wait_access_rules_timeout
145
-        rules_added = False
146 129
         tries = 0
147
-        rules = self.db.share_access_get_all_for_share(context, share['id'])
148
-        while not rules_added:
149
-            rules_added = True
150
-            tries += 1
151
-            now = time.time()
152
-            for access in rules:
153
-                if access['state'] != constants.STATUS_ACTIVE:
154
-                    rules_added = False
155
-            if rules_added:
156
-                break
157
-            if now > deadline:
158
-                msg = _("Timeout adding access rules for share "
159
-                        "%(share_id)s. Timeout was %(timeout)s seconds.") % {
160
-                    'share_id': share['id'],
161
-                    'timeout': self.migration_wait_access_rules_timeout}
162
-                raise exception.ShareMigrationFailed(reason=msg)
163
-            else:
164
-                time.sleep(tries ** 2)
165
-            rules = self.db.share_access_get_all_for_share(
166
-                context, share['id'])
167 130
 
168
-    def wait_for_allow_access(self, access_ref):
131
+        while True:
132
+            instance = self.db.share_instance_get(
133
+                self.context, share_instance['id'])
134
+
135
+            if instance['access_rules_status'] == constants.STATUS_ACTIVE:
136
+                break
169 137
 
170
-        starttime = time.time()
171
-        deadline = starttime + self.migration_wait_access_rules_timeout
172
-        tries = 0
173
-        rule = self.api.access_get(self.context, access_ref['id'])
174
-        while rule['state'] != constants.STATUS_ACTIVE:
175 138
             tries += 1
176 139
             now = time.time()
177
-            if rule['state'] == constants.STATUS_ERROR:
178
-                msg = _("Failed to allow access"
179
-                        " on share %s") % self.share['id']
140
+            if instance['access_rules_status'] == constants.STATUS_ERROR:
141
+                msg = _("Failed to update access rules"
142
+                        " on share instance %s") % share_instance['id']
180 143
                 raise exception.ShareMigrationFailed(reason=msg)
181 144
             elif now > deadline:
182
-                msg = _("Timeout trying to allow access"
183
-                        " on share %(share_id)s. Timeout "
145
+                msg = _("Timeout trying to update access rules"
146
+                        " on share instance %(share_id)s. Timeout "
184 147
                         "was %(timeout)s seconds.") % {
185
-                    'share_id': self.share['id'],
148
+                    'share_id': share_instance['id'],
186 149
                     'timeout': self.migration_wait_access_rules_timeout}
187 150
                 raise exception.ShareMigrationFailed(reason=msg)
188 151
             else:
189 152
                 time.sleep(tries ** 2)
190
-            rule = self.api.access_get(self.context, access_ref['id'])
191
-
192
-        return rule
193
-
194
-    def wait_for_deny_access(self, access_ref):
195
-
196
-        starttime = time.time()
197
-        deadline = starttime + self.migration_wait_access_rules_timeout
198
-        tries = -1
199
-        rule = "Something not None"
200
-        while rule:
201
-            try:
202
-                rule = self.api.access_get(self.context, access_ref['id'])
203
-                tries += 1
204
-                now = time.time()
205
-                if now > deadline:
206
-                    msg = _("Timeout trying to deny access"
207
-                            " on share %(share_id)s. Timeout "
208
-                            "was %(timeout)s seconds.") % {
209
-                        'share_id': self.share['id'],
210
-                        'timeout': self.migration_wait_access_rules_timeout}
211
-                    raise exception.ShareMigrationFailed(reason=msg)
212
-            except exception.NotFound:
213
-                rule = None
214
-            else:
215
-                time.sleep(tries ** 2)
216 153
 
217 154
     def allow_migration_access(self, access):
218 155
         allowed = False
@@ -234,7 +171,7 @@ class ShareMigrationHelper(object):
234 171
                     access_ref = access_item
235 172
 
236 173
         if access_ref and allowed:
237
-            access_ref = self.wait_for_allow_access(access_ref)
174
+            self.wait_for_access_update(self.share.instance)
238 175
 
239 176
         return access_ref
240 177
 
@@ -273,7 +210,7 @@ class ShareMigrationHelper(object):
273 210
                     raise
274 211
 
275 212
             if denied:
276
-                self.wait_for_deny_access(access_ref)
213
+                self.wait_for_access_update(self.share.instance)
277 214
 
278 215
     # NOTE(ganso): Cleanup methods do not throw exception, since the
279 216
     # exceptions that should be thrown are the ones that call the cleanup

+ 13
- 5
manila/share/rpcapi.py View File

@@ -45,6 +45,7 @@ class ShareAPI(object):
45 45
             migrate_share()
46 46
             get_migration_info()
47 47
             get_driver_migration_info()
48
+        1.7 - Update target call API in allow/deny access methods
48 49
     """
49 50
 
50 51
     BASE_RPC_API_VERSION = '1.0'
@@ -53,7 +54,7 @@ class ShareAPI(object):
53 54
         super(ShareAPI, self).__init__()
54 55
         target = messaging.Target(topic=CONF.share_topic,
55 56
                                   version=self.BASE_RPC_API_VERSION)
56
-        self.client = rpc.get_client(target, version_cap='1.6')
57
+        self.client = rpc.get_client(target, version_cap='1.7')
57 58
 
58 59
     def create_share_instance(self, ctxt, share_instance, host,
59 60
                               request_spec, filter_properties,
@@ -131,19 +132,26 @@ class ShareAPI(object):
131 132
         cctxt = self.client.prepare(server=new_host)
132 133
         cctxt.cast(ctxt, 'delete_snapshot', snapshot_id=snapshot['id'])
133 134
 
135
+    @staticmethod
136
+    def _get_access_rules(access):
137
+        if isinstance(access, list):
138
+            return [rule['id'] for rule in access]
139
+        else:
140
+            return [access['id']]
141
+
134 142
     def allow_access(self, ctxt, share_instance, access):
135 143
         host = utils.extract_host(share_instance['host'])
136
-        cctxt = self.client.prepare(server=host, version='1.4')
144
+        cctxt = self.client.prepare(server=host, version='1.7')
137 145
         cctxt.cast(ctxt, 'allow_access',
138 146
                    share_instance_id=share_instance['id'],
139
-                   access_id=access['id'])
147
+                   access_rules=self._get_access_rules(access))
140 148
 
141 149
     def deny_access(self, ctxt, share_instance, access):
142 150
         host = utils.extract_host(share_instance['host'])
143
-        cctxt = self.client.prepare(server=host, version='1.4')
151
+        cctxt = self.client.prepare(server=host, version='1.7')
144 152
         cctxt.cast(ctxt, 'deny_access',
145 153
                    share_instance_id=share_instance['id'],
146
-                   access_id=access['id'])
154
+                   access_rules=self._get_access_rules(access))
147 155
 
148 156
     def publish_service_capabilities(self, ctxt):
149 157
         cctxt = self.client.prepare(fanout=True, version='1.0')

+ 1
- 0
manila/tests/api/contrib/stubs.py View File

@@ -32,6 +32,7 @@ def stub_share(id, **kwargs):
32 32
         'host': 'fakehost',
33 33
         'size': 1,
34 34
         'availability_zone': 'fakeaz',
35
+        'access_rules_status': 'active',
35 36
         'status': 'fakestatus',
36 37
         'display_name': 'displayname',
37 38
         'display_description': 'displaydesc',

+ 98
- 0
manila/tests/db/migrations/alembic/migrations_data_checks.py View File

@@ -253,3 +253,101 @@ class ShareInstanceExportLocationMetadataChecks(BaseMigrationChecks):
253 253
         self.test_case.assertRaises(
254 254
             sa_exc.NoSuchTableError,
255 255
             utils.load_table, self.elm_table_name, engine)
256
+
257
+
258
+@map_to_migration('344c1ac4747f')
259
+class AccessRulesStatusMigrationChecks(BaseMigrationChecks):
260
+
261
+    def _get_instance_data(self, data):
262
+        base_dict = {}
263
+        base_dict.update(data)
264
+        return base_dict
265
+
266
+    def setup_upgrade_data(self, engine):
267
+
268
+        share_table = utils.load_table('shares', engine)
269
+
270
+        share = {
271
+            'id': 1,
272
+            'share_proto': "NFS",
273
+            'size': 0,
274
+            'snapshot_id': None,
275
+            'user_id': 'fake',
276
+            'project_id': 'fake',
277
+        }
278
+
279
+        engine.execute(share_table.insert(share))
280
+
281
+        rules1 = [
282
+            {'id': 'r1', 'share_instance_id': 1, 'state': 'active',
283
+             'deleted': 'False'},
284
+            {'id': 'r2', 'share_instance_id': 1, 'state': 'active',
285
+             'deleted': 'False'},
286
+            {'id': 'r3', 'share_instance_id': 1, 'state': 'deleting',
287
+             'deleted': 'False'},
288
+        ]
289
+        rules2 = [
290
+            {'id': 'r4', 'share_instance_id': 2, 'state': 'active',
291
+             'deleted': 'False'},
292
+            {'id': 'r5', 'share_instance_id': 2, 'state': 'error',
293
+             'deleted': 'False'},
294
+        ]
295
+
296
+        rules3 = [
297
+            {'id': 'r6', 'share_instance_id': 3, 'state': 'new',
298
+             'deleted': 'False'},
299
+        ]
300
+
301
+        instance_fixtures = [
302
+            {'id': 1, 'deleted': 'False', 'host': 'fake1', 'share_id': 1,
303
+             'status': 'available', 'rules': rules1},
304
+            {'id': 2, 'deleted': 'False', 'host': 'fake2', 'share_id': 1,
305
+             'status': 'available', 'rules': rules2},
306
+            {'id': 3, 'deleted': 'False', 'host': 'fake3', 'share_id': 1,
307
+             'status': 'available', 'rules': rules3},
308
+            {'id': 4, 'deleted': 'False', 'host': 'fake4', 'share_id': 1,
309
+             'status': 'deleting', 'rules': []},
310
+        ]
311
+
312
+        share_instances_table = utils.load_table('share_instances', engine)
313
+        share_instances_rules_table = utils.load_table(
314
+            'share_instance_access_map', engine)
315
+
316
+        for fixture in instance_fixtures:
317
+            rules = fixture.pop('rules')
318
+            engine.execute(share_instances_table.insert(fixture))
319
+
320
+            for rule in rules:
321
+                engine.execute(share_instances_rules_table.insert(rule))
322
+
323
+    def check_upgrade(self, engine, _):
324
+        instances_table = utils.load_table('share_instances', engine)
325
+
326
+        valid_statuses = {
327
+            '1': 'active',
328
+            '2': 'error',
329
+            '3': 'out_of_sync',
330
+            '4': None,
331
+        }
332
+
333
+        instances = engine.execute(instances_table.select().where(
334
+            instances_table.c.id in valid_statuses.keys()))
335
+
336
+        for instance in instances:
337
+            self.test_case.assertEqual(valid_statuses[instance['id']],
338
+                                       instance['access_rules_status'])
339
+
340
+    def check_downgrade(self, engine):
341
+        share_instances_rules_table = utils.load_table(
342
+            'share_instance_access_map', engine)
343
+
344
+        valid_statuses = {
345
+            '1': 'active',
346
+            '2': 'error',
347
+            '3': 'error',
348
+            '4': None,
349
+        }
350
+
351
+        for rule in engine.execute(share_instances_rules_table.select()):
352
+            valid_state = valid_statuses[rule['share_instance_id']]
353
+            self.test_case.assertEqual(valid_state, rule['state'])

+ 22
- 32
manila/tests/db/sqlalchemy/test_api.py View File

@@ -92,43 +92,33 @@ class ShareAccessDatabaseAPITestCase(test.TestCase):
92 92
         super(ShareAccessDatabaseAPITestCase, self).setUp()
93 93
         self.ctxt = context.get_admin_context()
94 94
 
95
-    @ddt.data(
96
-        {'statuses': (constants.STATUS_ACTIVE, constants.STATUS_ACTIVE,
97
-                      constants.STATUS_ACTIVE),
98
-         'valid': constants.STATUS_ACTIVE},
99
-        {'statuses': (constants.STATUS_ACTIVE, constants.STATUS_ACTIVE,
100
-                      constants.STATUS_NEW),
101
-         'valid': constants.STATUS_NEW},
102
-        {'statuses': (constants.STATUS_ACTIVE, constants.STATUS_ACTIVE,
103
-                      constants.STATUS_ERROR),
104
-         'valid': constants.STATUS_ERROR},
105
-        {'statuses': (constants.STATUS_DELETING, constants.STATUS_DELETED,
106
-                      constants.STATUS_ERROR),
107
-         'valid': constants.STATUS_ERROR},
108
-        {'statuses': (constants.STATUS_DELETING, constants.STATUS_DELETED,
109
-                      constants.STATUS_ACTIVE),
110
-         'valid': constants.STATUS_DELETING},
111
-        {'statuses': (constants.STATUS_DELETED, constants.STATUS_DELETED,
112
-                      constants.STATUS_DELETED),
113
-         'valid': constants.STATUS_DELETED},
114
-    )
115
-    @ddt.unpack
116
-    def test_share_access_state(self, statuses, valid):
95
+    def test_share_instance_update_access_status(self):
117 96
         share = db_utils.create_share()
118
-        db_utils.create_share_instance(share_id=share['id'])
119
-        db_utils.create_share_instance(share_id=share['id'])
97
+        share_instance = db_utils.create_share_instance(share_id=share['id'])
98
+        db_utils.create_access(share_id=share_instance['share_id'])
120 99
 
121
-        share = db_api.share_get(self.ctxt, share['id'])
122
-        access = db_utils.create_access(state=constants.STATUS_ACTIVE,
123
-                                        share_id=share['id'])
100
+        db_api.share_instance_update_access_status(
101
+            self.ctxt,
102
+            share_instance['id'],
103
+            constants.STATUS_ACTIVE
104
+        )
124 105
 
125
-        for index, mapping in enumerate(access.instance_mappings):
126
-            db_api.share_instance_access_update_state(
127
-                self.ctxt, mapping['id'], statuses[index])
106
+        result = db_api.share_instance_get(self.ctxt, share_instance['id'])
128 107
 
129
-        access = db_api.share_access_get(self.ctxt, access['id'])
108
+        self.assertEqual(constants.STATUS_ACTIVE,
109
+                         result['access_rules_status'])
130 110
 
131
-        self.assertEqual(valid, access.state)
111
+    def test_share_instance_update_access_status_invalid(self):
112
+        share = db_utils.create_share()
113
+        share_instance = db_utils.create_share_instance(share_id=share['id'])
114
+        db_utils.create_access(share_id=share_instance['share_id'])
115
+
116
+        self.assertRaises(
117
+            db_exception.DBError,
118
+            db_api.share_instance_update_access_status,
119
+            self.ctxt, share_instance['id'],
120
+            "fake_status"
121
+        )
132 122
 
133 123
 
134 124
 @ddt.ddt

+ 24
- 0
manila/tests/db/sqlalchemy/test_models.py View File

@@ -75,3 +75,27 @@ class ShareTestCase(test.TestCase):
75 75
         share = db_utils.create_share(status=constants.STATUS_CREATING)
76 76
 
77 77
         self.assertEqual(constants.STATUS_CREATING, share.instance['status'])
78
+
79
+    def test_access_rules_status_no_instances(self):
80
+        share = db_utils.create_share(instances=[])
81
+
82
+        self.assertEqual(constants.STATUS_ACTIVE, share.access_rules_status)
83
+
84
+    @ddt.data(constants.STATUS_ACTIVE, constants.STATUS_OUT_OF_SYNC,
85
+              constants.STATUS_ERROR)
86
+    def test_access_rules_status(self, access_status):
87
+        instances = [
88
+            db_utils.create_share_instance(
89
+                share_id='fake_id', status=constants.STATUS_ERROR,
90
+                access_rules_status=constants.STATUS_ACTIVE),
91
+            db_utils.create_share_instance(
92
+                share_id='fake_id', status=constants.STATUS_AVAILABLE,
93
+                access_rules_status=constants.STATUS_ACTIVE),
94
+            db_utils.create_share_instance(
95
+                share_id='fake_id', status=constants.STATUS_AVAILABLE,
96
+                access_rules_status=access_status),
97
+        ]
98
+
99
+        share = db_utils.create_share(instances=instances)
100
+
101
+        self.assertEqual(access_status, share.access_rules_status)

+ 119
- 0
manila/tests/share/test_access.py View File

@@ -0,0 +1,119 @@
1
+# Copyright 2016 Hitachi Data Systems inc.
2
+# All Rights Reserved.
3
+#
4
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+#    not use this file except in compliance with the License. You may obtain
6
+#    a copy of the License at
7
+#
8
+#         http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+#    Unless required by applicable law or agreed to in writing, software
11
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+#    License for the specific language governing permissions and limitations
14
+#    under the License.
15
+
16
+import mock
17
+
18
+from manila.common import constants
19
+from manila import context
20
+from manila import db
21
+from manila import exception
22
+from manila.share import access
23
+from manila import test
24
+from manila.tests import db_utils
25
+
26
+
27
+class ShareInstanceAccessTestCase(test.TestCase):
28
+    def setUp(self):
29
+        super(ShareInstanceAccessTestCase, self).setUp()
30
+        self.driver = self.mock_class("manila.share.driver.ShareDriver",
31
+                                      mock.Mock())
32
+        self.share_access_helper = access.ShareInstanceAccess(db, self.driver)
33
+        self.context = context.get_admin_context()
34
+        self.share = db_utils.create_share()
35
+        self.share_instance = db_utils.create_share_instance(
36
+            share_id=self.share['id'],
37
+            access_rules_status=constants.STATUS_ERROR)
38
+
39
+    def test_update_access_rules(self):
40
+        original_rules = []
41
+
42
+        self.mock_object(db, "share_instance_get", mock.Mock(
43
+            return_value=self.share_instance))
44
+        self.mock_object(db, "share_access_get_all_for_share",
45
+                         mock.Mock(return_value=original_rules))
46
+        self.mock_object(db, "share_instance_update_access_status",
47
+                         mock.Mock())
48
+        self.mock_object(self.driver, "update_access", mock.Mock())
49
+
50
+        self.share_access_helper.update_access_rules(self.context,
51
+                                                     self.share_instance['id'])
52
+
53
+        self.driver.update_access.assert_called_with(
54
+            self.context, self.share_instance, original_rules, add_rules=[],
55
+            delete_rules=[], share_server=None)
56
+        db.share_instance_update_access_status.assert_called_with(
57
+            self.context, self.share_instance['id'], constants.STATUS_ACTIVE)
58
+
59
+    def test_update_access_rules_fallback(self):
60
+        add_rules = [db_utils.create_access(share_id=self.share['id'])]
61
+        delete_rules = [db_utils.create_access(share_id=self.share['id'])]
62
+        original_rules = [db_utils.create_access(share_id=self.share['id'])]
63
+
64
+        self.mock_object(db, "share_instance_get", mock.Mock(
65
+            return_value=self.share_instance))
66
+        self.mock_object(db, "share_access_get_all_for_share",
67
+                         mock.Mock(return_value=original_rules))
68
+        self.mock_object(db, "share_instance_update_access_status",
69
+                         mock.Mock())
70
+        self.mock_object(self.driver, "update_access",
71
+                         mock.Mock(side_effect=NotImplementedError))
72
+        self.mock_object(self.driver, "allow_access",
73
+                         mock.Mock())
74
+        self.mock_object(self.driver, "deny_access",
75
+                         mock.Mock())
76
+
77
+        self.share_access_helper.update_access_rules(self.context,
78
+                                                     self.share_instance['id'],
79
+                                                     add_rules, delete_rules)
80
+
81
+        self.driver.update_access.assert_called_with(
82
+            self.context, self.share_instance, original_rules,
83
+            add_rules=add_rules, delete_rules=[], share_server=None)
84
+        self.driver.allow_access.assert_called_with(self.context,
85
+                                                    self.share_instance,
86
+                                                    add_rules[0],
87
+                                                    share_server=None)
88
+        self.driver.deny_access.assert_called_with(self.context,
89
+                                                   self.share_instance,
90
+                                                   delete_rules[0],
91
+                                                   share_server=None)
92
+        db.share_instance_update_access_status.assert_called_with(
93
+            self.context, self.share_instance['id'], constants.STATUS_ACTIVE)
94
+
95
+    def test_update_access_rules_exception(self):
96
+        original_rules = []
97
+        add_rules = [db_utils.create_access(share_id=self.share['id'])]
98
+        delete_rules = 'all'
99
+
100
+        self.mock_object(db, "share_instance_get", mock.Mock(
101
+            return_value=self.share_instance))
102
+        self.mock_object(db, "share_access_get_all_for_instance",
103
+                         mock.Mock(return_value=original_rules))
104
+        self.mock_object(db, "share_instance_update_access_status",
105
+                         mock.Mock())
106
+        self.mock_object(self.driver, "update_access",
107
+                         mock.Mock(side_effect=exception.ManilaException))
108
+
109
+        self.assertRaises(exception.ManilaException,
110
+                          self.share_access_helper.update_access_rules,
111
+                          self.context, self.share_instance['id'], add_rules,
112
+                          delete_rules)
113
+
114
+        self.driver.update_access.assert_called_with(
115
+            self.context, self.share_instance, [], add_rules=add_rules,
116
+            delete_rules=original_rules, share_server=None)
117
+
118
+        db.share_instance_update_access_status.assert_called_with(
119
+            self.context, self.share_instance['id'], constants.STATUS_ERROR)

+ 49
- 46
manila/tests/share/test_api.py View File

@@ -1293,7 +1293,7 @@ class ShareAPITestCase(test.TestCase):
1293 1293
         fake_access_expected = copy.deepcopy(values)
1294 1294
         fake_access_expected.update({
1295 1295
             'id': 'fake_access_id',
1296
-            'state': 'fake_state',
1296
+            'state': constants.STATUS_ACTIVE,
1297 1297
         })
1298 1298
         fake_access = copy.deepcopy(fake_access_expected)
1299 1299
         fake_access.update({
@@ -1303,6 +1303,8 @@ class ShareAPITestCase(test.TestCase):
1303 1303
         })
1304 1304
         self.mock_object(db_api, 'share_access_create',
1305 1305
                          mock.Mock(return_value=fake_access))
1306
+        self.mock_object(db_api, 'share_access_get',
1307
+                         mock.Mock(return_value=fake_access))
1306 1308
 
1307 1309
         access = self.api.allow_access(
1308 1310
             self.context, share, fake_access['access_type'],
@@ -1317,6 +1319,15 @@ class ShareAPITestCase(test.TestCase):
1317 1319
         share_api.policy.check_policy.assert_called_with(
1318 1320
             self.context, 'share', 'allow_access')
1319 1321
 
1322
+    def test_allow_access_existent_access(self):
1323
+        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
1324
+        fake_access = db_utils.create_access(share_id=share['id'])
1325
+
1326
+        self.assertRaises(exception.ShareAccessExists, self.api.allow_access,
1327
+                          self.context, share, fake_access['access_type'],
1328
+                          fake_access['access_to'], fake_access['access_level']
1329
+                          )
1330
+
1320 1331
     def test_allow_access_invalid_access_level(self):
1321 1332
         share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
1322 1333
         self.assertRaises(exception.InvalidShareAccess, self.api.allow_access,
@@ -1335,8 +1346,7 @@ class ShareAPITestCase(test.TestCase):
1335 1346
 
1336 1347
     def test_allow_access_to_instance(self):
1337 1348
         share = db_utils.create_share(host='fake')
1338
-        access = db_utils.create_access(share_id=share['id'],
1339
-                                        state=constants.STATUS_ACTIVE)
1349
+        access = db_utils.create_access(share_id=share['id'])
1340 1350
         rpc_method = self.mock_object(self.api.share_rpcapi, 'allow_access')
1341 1351
 
1342 1352
         self.api.allow_access_to_instance(self.context, share.instance, access)
@@ -1344,25 +1354,32 @@ class ShareAPITestCase(test.TestCase):
1344 1354
         rpc_method.assert_called_once_with(
1345 1355
             self.context, share.instance, access)
1346 1356
 
1357
+    def test_allow_access_to_instance_exception(self):
1358
+        share = db_utils.create_share(host='fake')
1359
+        access = db_utils.create_access(share_id=share['id'])
1360
+
1361
+        share.instance['access_rules_status'] = constants.STATUS_ERROR
1362
+
1363
+        self.assertRaises(exception.InvalidShareInstance,
1364
+                          self.api.allow_access_to_instance, self.context,
1365
+                          share.instance, access)
1366
+
1347 1367
     def test_deny_access_to_instance(self):
1348 1368
         share = db_utils.create_share(host='fake')
1349
-        access = db_utils.create_access(share_id=share['id'],
1350
-                                        state=constants.STATUS_ACTIVE)
1369
+        access = db_utils.create_access(share_id=share['id'])
1351 1370
         rpc_method = self.mock_object(self.api.share_rpcapi, 'deny_access')
1352 1371
         self.mock_object(db_api, 'share_instance_access_get',
1353 1372
                          mock.Mock(return_value=access.instance_mappings[0]))
1354
-        self.mock_object(db_api, 'share_instance_access_update_state')
1373
+        self.mock_object(db_api, 'share_instance_update_access_status')
1355 1374
 
1356 1375
         self.api.deny_access_to_instance(self.context, share.instance, access)
1357 1376
 
1358 1377
         rpc_method.assert_called_once_with(
1359 1378
             self.context, share.instance, access)
1360
-        db_api.share_instance_access_get.assert_called_once_with(
1361
-            self.context, access['id'], share.instance['id'])
1362
-        db_api.share_instance_access_update_state.assert_called_once_with(
1379
+        db_api.share_instance_update_access_status.assert_called_once_with(
1363 1380
             self.context,
1364
-            access.instance_mappings[0]['id'],
1365
-            constants.STATUS_DELETING
1381
+            share.instance['id'],
1382
+            constants.STATUS_OUT_OF_SYNC
1366 1383
         )
1367 1384
 
1368 1385
     @ddt.data('allow_access_to_instance', 'deny_access_to_instance')
@@ -1377,12 +1394,12 @@ class ShareAPITestCase(test.TestCase):
1377 1394
 
1378 1395
     @mock.patch.object(db_api, 'share_get', mock.Mock())
1379 1396
     @mock.patch.object(share_api.API, 'deny_access_to_instance', mock.Mock())
1380
-    @mock.patch.object(db_api, 'share_instance_access_get_all', mock.Mock())
1397
+    @mock.patch.object(db_api, 'share_instance_update_access_status',
1398
+                       mock.Mock())
1381 1399
     def test_deny_access_error(self):
1382 1400
         share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
1383 1401
         db_api.share_get.return_value = share
1384
-        access = db_utils.create_access(state=constants.STATUS_ERROR,
1385
-                                        share_id=share['id'])
1402
+        access = db_utils.create_access(share_id=share['id'])
1386 1403
         share_instance = share.instances[0]
1387 1404
         db_api.share_instance_access_get_all.return_value = [share_instance, ]
1388 1405
         self.api.deny_access(self.context, share, access)
@@ -1391,8 +1408,6 @@ class ShareAPITestCase(test.TestCase):
1391 1408
             self.context, 'share', 'deny_access')
1392 1409
         share_api.API.deny_access_to_instance.assert_called_once_with(
1393 1410
             self.context, share_instance, access)
1394
-        db_api.share_instance_access_get_all.assert_called_once_with(
1395
-            self.context, access['id'])
1396 1411
 
1397 1412
     @mock.patch.object(db_api, 'share_get', mock.Mock())
1398 1413
     @mock.patch.object(db_api, 'share_instance_access_get_all', mock.Mock())
@@ -1400,29 +1415,24 @@ class ShareAPITestCase(test.TestCase):
1400 1415
     def test_deny_access_error_no_share_instance_mapping(self):
1401 1416
         share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
1402 1417
         db_api.share_get.return_value = share
1403
-        access = db_utils.create_access(state=constants.STATUS_ERROR,
1404
-                                        share_id=share['id'])
1418
+        access = db_utils.create_access(share_id=share['id'])
1405 1419
         db_api.share_instance_access_get_all.return_value = []
1420
+
1406 1421
         self.api.deny_access(self.context, share, access)
1422
+
1407 1423
         db_api.share_get.assert_called_once_with(self.context, share['id'])
1408
-        share_api.policy.check_policy.assert_called_once_with(
1409
-            self.context, 'share', 'deny_access')
1410
-        db_api.share_access_delete.assert_called_once_with(
1411
-            self.context, access['id'])
1412
-        db_api.share_instance_access_get_all.assert_called_once_with(
1413
-            self.context, access['id'])
1424
+        self.assertTrue(share_api.policy.check_policy.called)
1414 1425
 
1415
-    @mock.patch.object(db_api, 'share_instance_access_update_state',
1426
+    @mock.patch.object(db_api, 'share_instance_update_access_status',
1416 1427
                        mock.Mock())
1417 1428
     def test_deny_access_active(self):
1418 1429
         share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
1419
-        access = db_utils.create_access(state=constants.STATUS_ACTIVE,
1420
-                                        share_id=share['id'])
1430
+        access = db_utils.create_access(share_id=share['id'])
1421 1431
         self.api.deny_access(self.context, share, access)
1422
-        db_api.share_instance_access_update_state.assert_called_once_with(
1432
+        db_api.share_instance_update_access_status.assert_called_once_with(
1423 1433
             self.context,
1424
-            access.instance_mappings[0]['id'],
1425
-            constants.STATUS_DELETING
1434
+            share.instance['id'],
1435
+            constants.STATUS_OUT_OF_SYNC
1426 1436
         )
1427 1437
         share_api.policy.check_policy.assert_called_with(
1428 1438
             self.context, 'share', 'deny_access')
@@ -1431,22 +1441,13 @@ class ShareAPITestCase(test.TestCase):
1431 1441
 
1432 1442
     def test_deny_access_not_found(self):
1433 1443
         share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
1434
-        access = db_utils.create_access(state=constants.STATUS_ACTIVE,
1435
-                                        share_id=share['id'])
1444
+        access = db_utils.create_access(share_id=share['id'])
1436 1445
         self.mock_object(db_api, 'share_instance_access_get',
1437 1446
                          mock.Mock(side_effect=[exception.NotFound('fake')]))
1438 1447
         self.api.deny_access(self.context, share, access)
1439 1448
         share_api.policy.check_policy.assert_called_with(
1440 1449
             self.context, 'share', 'deny_access')
1441 1450
 
1442
-    def test_deny_access_not_active_not_error(self):
1443
-        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
1444
-        access = db_utils.create_access(share_id=share['id'])
1445
-        self.assertRaises(exception.InvalidShareAccess, self.api.deny_access,
1446
-                          self.context, share, access)
1447
-        share_api.policy.check_policy.assert_called_once_with(
1448
-            self.context, 'share', 'deny_access')
1449
-
1450 1451
     def test_deny_access_status_not_available(self):
1451 1452
         share = db_utils.create_share(status=constants.STATUS_ERROR)
1452 1453
         self.assertRaises(exception.InvalidShare, self.api.deny_access,
@@ -1475,13 +1476,12 @@ class ShareAPITestCase(test.TestCase):
1475 1476
     def test_access_get_all(self):
1476 1477
         share = db_utils.create_share(id='fakeid')
1477 1478
 
1478
-        expected = {
1479
+        values = {
1479 1480
             'fakeacc0id': {
1480 1481
                 'id': 'fakeacc0id',
1481 1482
                 'access_type': 'fakeacctype',
1482 1483
                 'access_to': 'fakeaccto',
1483 1484
                 'access_level': 'rw',
1484
-                'state': constants.STATUS_ACTIVE,
1485 1485
                 'share_id': share['id'],
1486 1486
             },
1487 1487
             'fakeacc1id': {
@@ -1489,20 +1489,23 @@ class ShareAPITestCase(test.TestCase):
1489 1489
                 'access_type': 'fakeacctype',
1490 1490
                 'access_to': 'fakeaccto',
1491 1491
                 'access_level': 'rw',
1492
-                'state': constants.STATUS_DELETING,
1493 1492
                 'share_id': share['id'],
1494 1493
             },
1495 1494
         }
1496 1495
         rules = [
1497
-            db_utils.create_access(**expected['fakeacc0id']),
1498
-            db_utils.create_access(**expected['fakeacc1id']),
1496
+            db_utils.create_access(**values['fakeacc0id']),
1497
+            db_utils.create_access(**values['fakeacc1id']),
1499 1498
         ]
1500 1499
 
1500
+        # add state property
1501
+        values['fakeacc0id']['state'] = constants.STATUS_ACTIVE
1502
+        values['fakeacc1id']['state'] = constants.STATUS_ACTIVE
1503
+
1501 1504
         self.mock_object(db_api, 'share_access_get_all_for_share',
1502 1505
                          mock.Mock(return_value=rules))
1503 1506
         actual = self.api.access_get_all(self.context, share)
1504 1507
         for access in actual:
1505
-            expected_access = expected[access['id']]
1508
+            expected_access = values[access['id']]
1506 1509
             expected_access.pop('share_id')
1507 1510
             self.assertEqual(expected_access, access)
1508 1511
 

+ 10
- 0
manila/tests/share/test_driver.py View File

@@ -689,3 +689,13 @@ class ShareDriverTestCase(test.TestCase):
689 689
         self.assertRaises(exception.ShareMigrationFailed,
690 690
                           share_driver.copy_share_data, 'ctx', None,
691 691
                           fake_share, None, None, None, None, local, remote)
692
+
693
+    def test_update_access(self):
694
+        share_driver = driver.ShareDriver(True, configuration=None)
695
+        self.assertRaises(
696
+            NotImplementedError,
697
+            share_driver.update_access,
698
+            'ctx',
699
+            'fake_share',
700
+            'fake_access_rules'
701
+        )

+ 73
- 98
manila/tests/share/test_manager.py View File

@@ -29,6 +29,7 @@ from manila import db
29 29
 from manila.db.sqlalchemy import models
30 30
 from manila import exception
31 31
 from manila import quota
32
+from manila.share import access as share_access
32 33
 from manila.share import drivers_private_data
33 34
 from manila.share import manager
34 35
 from manila.share import migration
@@ -203,15 +204,19 @@ class ShareManagerTestCase(test.TestCase):
203 204
                 status=constants.STATUS_AVAILABLE,
204 205
                 task_state=constants.STATUS_TASK_STATE_MIGRATION_IN_PROGRESS,
205 206
                 display_name='fake_name_4').instance,
207
+            db_utils.create_share(id='fake_id_5',
208
+                                  status=constants.STATUS_AVAILABLE,
209
+                                  display_name='fake_name_5').instance,
206 210
         ]
211
+
212
+        instances[4]['access_rules_status'] = constants.STATUS_OUT_OF_SYNC
213
+
207 214
         if not setup_access_rules:
208 215
             return instances
209 216
 
210 217
         rules = [
211
-            db_utils.create_access(state=constants.STATUS_ACTIVE,
212
-                                   share_id='fake_id_1'),
213
-            db_utils.create_access(state=constants.STATUS_ERROR,
214
-                                   share_id='fake_id_3'),
218
+            db_utils.create_access(share_id='fake_id_1'),
219
+            db_utils.create_access(share_id='fake_id_3'),
215 220
         ]
216 221
 
217 222
         return instances, rules
@@ -231,7 +236,7 @@ class ShareManagerTestCase(test.TestCase):
231 236
                          mock.Mock(return_value=instances))
232 237
         self.mock_object(self.share_manager.db, 'share_instance_get',
233 238
                          mock.Mock(side_effect=[instances[0], instances[2],
234
-                                                instances[3]]))
239
+                                                instances[4]]))
235 240
         self.mock_object(self.share_manager.db,
236 241
                          'share_export_locations_update')
237 242
         self.mock_object(self.share_manager.driver, 'ensure_share',
@@ -244,8 +249,11 @@ class ShareManagerTestCase(test.TestCase):
244 249
         self.mock_object(self.share_manager.db,
245 250
                          'share_access_get_all_for_share',
246 251
                          mock.Mock(return_value=rules))
247
-        self.mock_object(self.share_manager.driver, 'allow_access',
248
-                         mock.Mock(side_effect=raise_share_access_exists))
252
+        self.mock_object(
253
+            self.share_manager.access_helper,
254
+            'update_access_rules',
255
+            mock.Mock(side_effect=raise_share_access_exists)
256
+        )
249 257
 
250 258
         # call of 'init_host' method
251 259
         self.share_manager.init_host()
@@ -277,20 +285,11 @@ class ShareManagerTestCase(test.TestCase):
277 285
             mock.call(utils.IsAMatcher(context.RequestContext), instances[2],
278 286
                       share_server=share_server),
279 287
         ])
280
-        self.share_manager.db.share_access_get_all_for_share.assert_has_calls([
281
-            mock.call(utils.IsAMatcher(context.RequestContext),
282
-                      instances[0]['share_id']),
283
-            mock.call(utils.IsAMatcher(context.RequestContext),
284
-                      instances[2]['share_id']),
285
-        ])
286 288
         self.share_manager.publish_service_capabilities.\
287 289
             assert_called_once_with(
288 290
                 utils.IsAMatcher(context.RequestContext))
289
-        self.share_manager.driver.allow_access.assert_has_calls([
290
-            mock.call(mock.ANY, instances[0], rules[0],
291
-                      share_server=share_server),
292
-            mock.call(mock.ANY, instances[2], rules[0],
293
-                      share_server=share_server),
291
+        self.share_manager.access_helper.update_access_rules.assert_has_calls([
292
+            mock.call(mock.ANY, instances[4]['id'], share_server=share_server),
294 293
         ])
295 294
 
296 295
     def test_init_host_with_exception_on_ensure_share(self):
@@ -351,51 +350,50 @@ class ShareManagerTestCase(test.TestCase):
351 350
             {'id': instances[1]['id'], 'status': instances[1]['status']},
352 351
         )
353 352
 
354
-    def test_init_host_with_exception_on_rule_access_allow(self):
353
+    def test_init_host_with_exception_on_update_access_rules(self):
355 354
         def raise_exception(*args, **kwargs):
356 355
             raise exception.ManilaException(message="Fake raise")
357 356
 
358 357
         instances, rules = self._setup_init_mocks()
359 358
         share_server = 'fake_share_server_type_does_not_matter'
360
-        self.mock_object(self.share_manager.db,
361
-                         'share_instances_get_all_by_host',
359
+        smanager = self.share_manager
360
+        self.mock_object(smanager.db, 'share_instances_get_all_by_host',
362 361
                          mock.Mock(return_value=instances))
363 362
         self.mock_object(self.share_manager.db, 'share_instance_get',
364 363
                          mock.Mock(side_effect=[instances[0], instances[2],
365
-                                                instances[3]]))
364
+                                                instances[4]]))
366 365
         self.mock_object(self.share_manager.driver, 'ensure_share',
367 366
                          mock.Mock(return_value=None))
368
-        self.mock_object(self.share_manager, '_ensure_share_instance_has_pool')
369
-        self.mock_object(self.share_manager, '_get_share_server',
367
+        self.mock_object(smanager, '_ensure_share_instance_has_pool')
368
+        self.mock_object(smanager, '_get_share_server',
370 369
                          mock.Mock(return_value=share_server))
371
-        self.mock_object(self.share_manager, 'publish_service_capabilities')
370
+        self.mock_object(smanager, 'publish_service_capabilities')
372 371
         self.mock_object(manager.LOG, 'error')
373 372
         self.mock_object(manager.LOG, 'info')
374
-        self.mock_object(self.share_manager.db,
375
-                         'share_access_get_all_for_share',
373
+        self.mock_object(smanager.db, 'share_access_get_all_for_share',
376 374
                          mock.Mock(return_value=rules))
377
-        self.mock_object(self.share_manager.driver, 'allow_access',
375
+        self.mock_object(smanager.access_helper, 'update_access_rules',
378 376
                          mock.Mock(side_effect=raise_exception))
379 377
 
380 378
         # call of 'init_host' method
381
-        self.share_manager.init_host()
379
+        smanager.init_host()
382 380
 
383 381
         # verification of call
384
-        self.share_manager.db.share_instances_get_all_by_host.\
382
+        smanager.db.share_instances_get_all_by_host.\
385 383
             assert_called_once_with(utils.IsAMatcher(context.RequestContext),
386
-                                    self.share_manager.host)
387
-        self.share_manager.driver.do_setup.assert_called_once_with(
384
+                                    smanager.host)
385
+        smanager.driver.do_setup.assert_called_once_with(
388 386
             utils.IsAMatcher(context.RequestContext))
389
-        self.share_manager.driver.check_for_setup_error.assert_called_with()
390
-        self.share_manager._ensure_share_instance_has_pool.assert_has_calls([
387
+        smanager.driver.check_for_setup_error.assert_called_with()
388
+        smanager._ensure_share_instance_has_pool.assert_has_calls([
391 389
             mock.call(utils.IsAMatcher(context.RequestContext), instances[0]),
392 390
             mock.call(utils.IsAMatcher(context.RequestContext), instances[2]),
393 391
         ])
394
-        self.share_manager._get_share_server.assert_has_calls([
392
+        smanager._get_share_server.assert_has_calls([
395 393
             mock.call(utils.IsAMatcher(context.RequestContext), instances[0]),
396 394
             mock.call(utils.IsAMatcher(context.RequestContext), instances[2]),
397 395
         ])
398
-        self.share_manager.driver.ensure_share.assert_has_calls([
396
+        smanager.driver.ensure_share.assert_has_calls([
399 397
             mock.call(utils.IsAMatcher(context.RequestContext), instances[0],
400 398
                       share_server=share_server),
401 399
             mock.call(utils.IsAMatcher(context.RequestContext), instances[2],
@@ -413,15 +411,12 @@ class ShareManagerTestCase(test.TestCase):
413 411
             mock.ANY,
414 412
             {'id': instances[1]['id'], 'status': instances[1]['status']},
415 413
         )
416
-        self.share_manager.driver.allow_access.assert_has_calls([
417
-            mock.call(utils.IsAMatcher(context.RequestContext), instances[0],
418
-                      rules[0], share_server=share_server),
419
-            mock.call(utils.IsAMatcher(context.RequestContext), instances[2],
420
-                      rules[0], share_server=share_server),
414
+        smanager.access_helper.update_access_rules.assert_has_calls([
415
+            mock.call(utils.IsAMatcher(context.RequestContext),
416
+                      instances[4]['id'], share_server=share_server),
421 417
         ])
422 418
         manager.LOG.error.assert_has_calls([
423 419
             mock.call(mock.ANY, mock.ANY),
424
-            mock.call(mock.ANY, mock.ANY),
425 420
         ])
426 421
 
427 422
     def test_create_share_instance_from_snapshot_with_server(self):
@@ -1181,8 +1176,11 @@ class ShareManagerTestCase(test.TestCase):
1181 1176
         manager.CONF.unmanage_remove_access_rules = True
1182 1177
         self._setup_unmanage_mocks(mock_driver=False,
1183 1178
                                    mock_unmanage=mock.Mock())
1184
-        self.mock_object(self.share_manager, '_remove_share_access_rules',
1185
-                         mock.Mock(side_effect=Exception()))
1179
+        self.mock_object(
1180
+            self.share_manager.access_helper,
1181
+            'update_access_rules',
1182
+            mock.Mock(side_effect=Exception())
1183
+        )
1186 1184
         self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(return_value=[]))
1187 1185
         share = db_utils.create_share()
1188 1186
 
@@ -1196,37 +1194,22 @@ class ShareManagerTestCase(test.TestCase):
1196 1194
         manager.CONF.unmanage_remove_access_rules = True
1197 1195
         self._setup_unmanage_mocks(mock_driver=False,
1198 1196
                                    mock_unmanage=mock.Mock())
1199
-        self.mock_object(self.share_manager, '_remove_share_access_rules')
1197
+        smanager = self.share_manager
1198
+        self.mock_object(smanager.access_helper, 'update_access_rules')
1200 1199
         self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(return_value=[]))
1201 1200
         share = db_utils.create_share()
1202 1201
         share_id = share['id']
1203 1202
         share_instance_id = share.instance['id']
1204 1203
 
1205
-        self.share_manager.unmanage_share(self.context, share_id)
1204
+        smanager.unmanage_share(self.context, share_id)
1206 1205
 
1207
-        self.share_manager.driver.unmanage.\
1208
-            assert_called_once_with(mock.ANY)
1209
-        self.share_manager._remove_share_access_rules.assert_called_once_with(
1210
-            mock.ANY, mock.ANY, mock.ANY, mock.ANY
1206
+        smanager.driver.unmanage.assert_called_once_with(mock.ANY)
1207
+        smanager.access_helper.update_access_rules.assert_called_once_with(
1208
+            mock.ANY, mock.ANY, delete_rules='all', share_server=None
1211 1209
         )
1212
-        self.share_manager.db.share_instance_delete.assert_called_once_with(
1210
+        smanager.db.share_instance_delete.assert_called_once_with(
1213 1211
             mock.ANY, share_instance_id)
1214 1212
 
1215
-    def test_remove_share_access_rules(self):
1216
-        self.mock_object(self.share_manager.db,
1217
-                         'share_access_get_all_for_share',
1218
-                         mock.Mock(return_value=['fake_ref', 'fake_ref2']))
1219
-        self.mock_object(self.share_manager, '_deny_access')
1220
-        share_ref = db_utils.create_share()
1221
-        share_server = 'fake'
1222
-
1223
-        self.share_manager._remove_share_access_rules(
1224
-            self.context, share_ref, share_ref.instance, share_server)
1225
-
1226
-        self.share_manager.db.share_access_get_all_for_share.\
1227
-            assert_called_once_with(mock.ANY, share_ref['id'])
1228
-        self.assertEqual(2, self.share_manager._deny_access.call_count)
1229
-
1230 1213
     def test_delete_share_instance_share_server_not_found(self):
1231 1214
         share_net = db_utils.create_share_network()
1232 1215
         share = db_utils.create_share(share_network_id=share_net['id'],
@@ -1322,28 +1305,27 @@ class ShareManagerTestCase(test.TestCase):
1322 1305
 
1323 1306
     def test_allow_deny_access(self):
1324 1307
         """Test access rules to share can be created and deleted."""
1325
-        self.mock_object(manager.LOG, 'info')
1308
+        self.mock_object(share_access.LOG, 'info')
1326 1309
 
1327 1310
         share = db_utils.create_share()
1328 1311
         share_id = share['id']
1329 1312
         access = db_utils.create_access(share_id=share_id)
1330 1313
         access_id = access['id']
1331 1314
         self.share_manager.allow_access(self.context, share.instance['id'],
1332
-                                        access_id)
1333
-        self.assertEqual('active', db.share_access_get(self.context,
1334
-                                                       access_id).state)
1315
+                                        [access_id])
1316
+        self.assertEqual('active', db.share_instance_get(
1317
+            self.context, share.instance['id']).access_rules_status)
1335 1318
 
1336
-        exp_args = {'access_level': access['access_level'],
1337
-                    'share_instance_id': share.instance['id'],
1338
-                    'access_to': access['access_to']}
1339
-        manager.LOG.info.assert_called_with(mock.ANY, exp_args)
1340
-        manager.LOG.info.reset_mock()
1319
+        share_access.LOG.info.assert_called_with(mock.ANY,
1320
+                                                 share.instance['id'])
1321
+        share_access.LOG.info.reset_mock()
1341 1322
 
1342 1323
         self.share_manager.deny_access(self.context, share.instance['id'],
1343
-                                       access_id)
1344
-        exp_args = {'share_instance_id': share.instance['id'],
1345
-                    'access_to': access['access_to']}
1346
-        manager.LOG.info.assert_called_with(mock.ANY, exp_args)
1324
+                                       [access_id])
1325
+
1326
+        share_access.LOG.info.assert_called_with(mock.ANY,
1327
+                                                 share.instance['id'])
1328
+        share_access.LOG.info.reset_mock()
1347 1329
 
1348 1330
     def test_allow_deny_access_error(self):
1349 1331
         """Test access rules to share can be created and deleted with error."""
@@ -1354,33 +1336,26 @@ class ShareManagerTestCase(test.TestCase):
1354 1336
         def _fake_deny_access(self, *args, **kwargs):
1355 1337
             raise exception.NotFound()
1356 1338
 
1357
-        self.mock_object(self.share_manager.driver, "allow_access",
1358
-                         _fake_allow_access)
1359
-        self.mock_object(self.share_manager.driver, "deny_access",
1360
-                         _fake_deny_access)
1339
+        self.mock_object(self.share_manager.access_helper.driver,
1340
+                         "allow_access", _fake_allow_access)
1341
+        self.mock_object(self.share_manager.access_helper.driver,
1342
+                         "deny_access", _fake_deny_access)
1361 1343
 
1362 1344
         share = db_utils.create_share()
1363 1345
         share_id = share['id']
1364 1346
         access = db_utils.create_access(share_id=share_id)
1365 1347
         access_id = access['id']
1366 1348
 
1367
-        self.assertRaises(exception.NotFound,
1368
-                          self.share_manager.allow_access,
1369
-                          self.context,
1370
-                          share.instance['id'],
1371
-                          access_id)
1372
-
1373
-        acs = db.share_access_get(self.context, access_id)
1374
-        self.assertEqual(constants.STATUS_ERROR, acs['state'])
1349
+        def validate(method):
1350
+            self.assertRaises(exception.ManilaException, method, self.context,
1351
+                              share.instance['id'], [access_id])
1375 1352
 
1376
-        self.assertRaises(exception.NotFound,
1377
-                          self.share_manager.deny_access,
1378
-                          self.context,
1379
-                          share.instance['id'],
1380
-                          access_id)
1353
+            inst = db.share_instance_get(self.context, share.instance['id'])
1354
+            self.assertEqual(constants.STATUS_ERROR,
1355
+                             inst['access_rules_status'])
1381 1356
 
1382
-        acs = db.share_access_get(self.context, access_id)
1383
-        self.assertEqual(constants.STATUS_ERROR, acs['state'])
1357
+        validate(self.share_manager.allow_access)
1358
+        validate(self.share_manager.deny_access)
1384 1359
 
1385 1360
     def test_setup_server(self):
1386 1361
         # Setup required test data

+ 75
- 168
manila/tests/share/test_migration.py View File

@@ -44,100 +44,44 @@ class ShareMigrationHelperTestCase(test.TestCase):
44 44
             driver.CONF.migration_wait_access_rules_timeout, self.share)
45 45
 
46 46
     def test_deny_rules_and_wait(self):
47
-        saved_rules = [db_utils.create_access(share_id=self.share['id'],
48
-                       state=constants.STATUS_ACTIVE)]
47
+        saved_rules = [db_utils.create_access(share_id=self.share['id'])]
48
+
49
+        fake_share_instances = [
50
+            {"id": "1", "access_rules_status": constants.STATUS_OUT_OF_SYNC},
51
+            {"id": "1", "access_rules_status": constants.STATUS_ACTIVE},
52
+        ]
49 53
 
50 54
         self.mock_object(share_api.API, 'deny_access_to_instance')
51
-        self.mock_object(db, 'share_access_get_all_for_share',
52
-                         mock.Mock(side_effect=[saved_rules, []]))
55
+        self.mock_object(db, 'share_instance_get',
56
+                         mock.Mock(side_effect=fake_share_instances))
53 57
         self.mock_object(time, 'sleep')
54 58
 
55 59
         self.helper.deny_rules_and_wait(
56 60
             self.context, self.share, saved_rules)
57 61
 
58
-        db.share_access_get_all_for_share.assert_any_call(
59
-            self.context, self.share['id'])
60
-
61
-    def test_deny_rules_and_wait_timeout(self):
62
-
63
-        saved_rules = [db_utils.create_access(share_id=self.share['id'],
64
-                       state=constants.STATUS_ACTIVE)]
65
-
66
-        self.mock_object(share_api.API, 'deny_access_to_instance')
67
-        self.mock_object(db, 'share_access_get_all_for_share',
68
-                         mock.Mock(return_value=saved_rules))
69
-        self.mock_object(time, 'sleep')
70
-
71
-        now = time.time()
72
-        timeout = now + 100
73
-
74
-        self.mock_object(time, 'time',
75
-                         mock.Mock(side_effect=[now, timeout]))
76
-
77
-        self.assertRaises(exception.ShareMigrationFailed,
78
-                          self.helper.deny_rules_and_wait,
79
-                          self.context, self.share, saved_rules)
80
-
81
-        db.share_access_get_all_for_share.assert_called_once_with(
82
-            self.context, self.share['id'])
62
+        db.share_instance_get.assert_any_call(
63
+            self.context, self.share.instance['id'])
83 64
 
84 65
     def test_add_rules_and_wait(self):
85 66
 
86
-        rules_active = [db_utils.create_access(share_id=self.share['id'],
87
-                        state=constants.STATUS_ACTIVE)]
88
-        rules_new = [db_utils.create_access(share_id=self.share['id'],
89
-                     state=constants.STATUS_NEW)]
90
-
91
-        self.mock_object(share_api.API, 'allow_access')
92
-        self.mock_object(db, 'share_access_get_all_for_share',
93
-                         mock.Mock(side_effect=[rules_new,
94
-                                                rules_active]))
95
-        self.mock_object(time, 'sleep')
96
-
97
-        self.helper.add_rules_and_wait(self.context, self.share,
98
-                                       rules_active)
67
+        fake_access_rules = [
68
+            {'access_type': 'fake', 'access_level': 'ro', 'access_to': 'fake'},
69
+            {'access_type': 'f0ke', 'access_level': 'rw', 'access_to': 'f0ke'},
70
+        ]
99 71
 
100
-        db.share_access_get_all_for_share.assert_any_call(
101
-            self.context, self.share['id'])
102
-
103
-    def test_add_rules_and_wait_access_level(self):
104
-
105
-        rules_active = [db_utils.create_access(share_id=self.share['id'],
106
-                        state=constants.STATUS_ACTIVE)]
107
-
108
-        self.mock_object(share_api.API, 'allow_access')
109
-        self.mock_object(db, 'share_access_get_all_for_share',
110
-                         mock.Mock(return_value=rules_active))
111
-        self.mock_object(time, 'sleep')
72
+        self.mock_object(share_api.API, 'allow_access_to_instance')
73
+        self.mock_object(self.helper, 'wait_for_access_update')
74
+        self.mock_object(db, 'share_access_create')
112 75
 
113 76
         self.helper.add_rules_and_wait(self.context, self.share,
114
-                                       rules_active, 'access_level')
115
-
116
-        db.share_access_get_all_for_share.assert_any_call(
117
-            self.context, self.share['id'])
118
-
119
-    def test_add_rules_and_wait_timeout(self):
120
-
121
-        rules_new = [db_utils.create_access(share_id=self.share['id'],
122
-                     state=constants.STATUS_NEW)]
123
-
124
-        self.mock_object(share_api.API, 'allow_access')
125
-        self.mock_object(db, 'share_access_get_all_for_share',
126
-                         mock.Mock(return_value=rules_new))
127
-        self.mock_object(time, 'sleep')
128
-
129
-        now = time.time()
130
-        timeout = now + 100
77
+                                       fake_access_rules)
131 78
 
132
-        self.mock_object(time, 'time',
133
-                         mock.Mock(side_effect=[now, timeout]))
134
-
135
-        self.assertRaises(exception.ShareMigrationFailed,
136
-                          self.helper.add_rules_and_wait, self.context,
137
-                          self.share, rules_new)
138
-
139
-        db.share_access_get_all_for_share.assert_called_once_with(
140
-            self.context, self.share['id'])
79
+        share_api.API.allow_access_to_instance.assert_called_once_with(
80
+            self.context, self.share.instance, mock.ANY
81
+        )
82
+        self.helper.wait_for_access_update.assert_called_once_with(
83
+            self.share.instance
84
+        )
141 85
 
142 86
     def test_delete_instance_and_wait(self):
143 87
 
@@ -258,71 +202,39 @@ class ShareMigrationHelperTestCase(test.TestCase):
258 202
         db.share_instance_get.assert_called_once_with(
259 203
             self.context, share_instance_creating['id'], with_share_data=True)
260 204
 
261
-    def test_wait_for_allow_access(self):
262
-
263
-        access_active = db_utils.create_access(state=constants.STATUS_ACTIVE,
264
-                                               share_id=self.share['id'])
265
-        access_new = db_utils.create_access(state=constants.STATUS_NEW,
266
-                                            share_id=self.share['id'])
205
+    def test_wait_for_access_update(self):
206
+        sid = 1
207
+        fake_share_instances = [
208
+            {'id': sid, 'access_rules_status': constants.STATUS_OUT_OF_SYNC},
209
+            {'id': sid, 'access_rules_status': constants.STATUS_ACTIVE},
210
+        ]
267 211
 
268 212
         self.mock_object(time, 'sleep')
269
-
270
-        self.mock_object(self.helper.api, 'access_get',
271
-                         mock.Mock(side_effect=[access_new, access_active]))
272
-
273
-        result = self.helper.wait_for_allow_access(access_new)
274
-
275
-        self.assertEqual(access_active, result)
276
-
277
-    def test_wait_for_allow_access_timeout(self):
278
-
279
-        access_new = db_utils.create_access(state=constants.STATUS_NEW,
280
-                                            share_id=self.share['id'])
281
-
282
-        self.mock_object(self.helper.api, 'access_get',
283
-                         mock.Mock(return_value=access_new))
284
-
285
-        now = time.time()
286
-        timeout = now + 100
287
-
288
-        self.mock_object(time, 'time',
289
-                         mock.Mock(side_effect=[now, timeout]))
290
-
291
-        self.assertRaises(exception.ShareMigrationFailed,
292
-                          self.helper.wait_for_allow_access, access_new)
293
-
294
-    def test_wait_for_allow_access_error(self):
295
-
296
-        access_new = db_utils.create_access(state=constants.STATUS_NEW,
297
-                                            share_id=self.share['id'])
298
-        access_error = db_utils.create_access(state=constants.STATUS_ERROR,
299
-                                              share_id=self.share['id'])
300
-
301
-        self.mock_object(self.helper.api, 'access_get',
302
-                         mock.Mock(return_value=access_error))
303
-
304