# Copyright 2016 Hitachi Data Systems inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import ddt
import mock
import random

import itertools
import six

from manila.common import constants
from manila import context
from manila import db
from manila import exception
from manila.share import access
from manila import test
from manila.tests import db_utils
from manila import utils


class LockedOperationsTestCase(test.TestCase):

    class FakeAccessHelper(object):

        @access.locked_access_rules_operation
        def some_access_rules_operation(self, context, share_instance_id=None):
            pass

    def setUp(self):
        super(LockedOperationsTestCase, self).setUp()
        self.access_helper = self.FakeAccessHelper()
        self.context = context.RequestContext('fake_user', 'fake_project')
        self.lock_call = self.mock_object(
            utils, 'synchronized', mock.Mock(return_value=lambda f: f))

    def test_locked_access_rules_operation(self, **replica):

        self.access_helper.some_access_rules_operation(
            self.context, share_instance_id='FAKE_INSTANCE_ID')

        self.lock_call.assert_called_once_with(
            "locked_access_rules_operation_by_share_instance_FAKE_INSTANCE_ID",
            external=True)


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

    def setUp(self):
        super(ShareInstanceAccessDatabaseMixinTestCase, self).setUp()
        self.driver = mock.Mock()
        self.access_helper = access.ShareInstanceAccess(db, self.driver)
        self.context = context.RequestContext('fake_user', 'fake_project')
        self.mock_object(
            utils, 'synchronized', mock.Mock(return_value=lambda f: f))

    def test_get_and_update_access_rules_status_force_status(self):
        share = db_utils.create_share(
            access_rule_status=constants.STATUS_ACTIVE,
            status=constants.STATUS_AVAILABLE)
        share = db.share_get(self.context, share['id'])
        self.assertEqual(constants.STATUS_ACTIVE, share['access_rules_status'])

        self.access_helper.get_and_update_share_instance_access_rules_status(
            self.context, status=constants.SHARE_INSTANCE_RULES_SYNCING,
            share_instance_id=share['instance']['id'])

        share = db.share_get(self.context, share['id'])
        self.assertEqual(constants.SHARE_INSTANCE_RULES_SYNCING,
                         share['access_rules_status'])

    @ddt.data((constants.SHARE_INSTANCE_RULES_SYNCING, True),
              (constants.STATUS_ERROR, False))
    @ddt.unpack
    def test_get_and_update_access_rules_status_conditionally_change(
            self, initial_status, change_allowed):
        share = db_utils.create_share(access_rules_status=initial_status,
                                      status=constants.STATUS_AVAILABLE)
        share = db.share_get(self.context, share['id'])
        self.assertEqual(initial_status, share['access_rules_status'])

        conditionally_change = {
            constants.SHARE_INSTANCE_RULES_SYNCING: constants.STATUS_ACTIVE,
        }

        updated_instance = (
            self.access_helper.
            get_and_update_share_instance_access_rules_status(
                self.context, conditionally_change=conditionally_change,
                share_instance_id=share['instance']['id'])
        )

        share = db.share_get(self.context, share['id'])
        if change_allowed:
            self.assertEqual(constants.STATUS_ACTIVE,
                             share['access_rules_status'])
            self.assertIsNotNone(updated_instance)
        else:
            self.assertEqual(initial_status, share['access_rules_status'])
            self.assertIsNone(updated_instance)

    def test_get_and_update_all_access_rules_just_get(self):
        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
        rule_1 = db_utils.create_access(share_id=share['id'])
        rule_2 = db_utils.create_access(share_id=share['id'])
        self.mock_object(db, 'share_instance_access_update')

        rules = self.access_helper.get_and_update_share_instance_access_rules(
            self.context, share_instance_id=share['instance']['id'])

        self.assertEqual(2, len(rules))
        rule_ids = [r['access_id'] for r in rules]
        self.assertIn(rule_1['id'], rule_ids)
        self.assertIn(rule_2['id'], rule_ids)
        self.assertFalse(db.share_instance_access_update.called)

    @ddt.data(
        ([constants.ACCESS_STATE_QUEUED_TO_APPLY], 2),
        ([constants.ACCESS_STATE_QUEUED_TO_APPLY,
          constants.STATUS_ACTIVE], 1),
        ([constants.ACCESS_STATE_APPLYING], 2),
        ([constants.ACCESS_STATE_APPLYING, constants.ACCESS_STATE_ERROR], 1),
        ([constants.ACCESS_STATE_ACTIVE, constants.ACCESS_STATE_DENYING], 0))
    @ddt.unpack
    def test_get_and_update_all_access_rules_updates_conditionally_changed(
            self, statuses, changes_allowed):
        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
        db_utils.create_access(share_id=share['id'], state=statuses[0])
        db_utils.create_access(share_id=share['id'], state=statuses[-1])
        self.mock_object(db, 'share_instance_access_update', mock.Mock(
            side_effect=db.share_instance_access_update))
        updates = {
            'access_key': 'renfrow2stars'
        }
        expected_updates = {
            'access_key': 'renfrow2stars',
            'state': constants.ACCESS_STATE_QUEUED_TO_DENY,
        }
        conditionally_change = {
            constants.ACCESS_STATE_APPLYING:
                constants.ACCESS_STATE_QUEUED_TO_DENY,
            constants.ACCESS_STATE_QUEUED_TO_APPLY:
                constants.ACCESS_STATE_QUEUED_TO_DENY,
        }

        rules = self.access_helper.get_and_update_share_instance_access_rules(
            self.context, share_instance_id=share['instance']['id'],
            updates=updates, conditionally_change=conditionally_change)

        state_changed_rules = [
            r for r in rules if
            r['state'] == constants.ACCESS_STATE_QUEUED_TO_DENY
        ]
        self.assertEqual(changes_allowed, len(state_changed_rules))
        self.assertEqual(2, db.share_instance_access_update.call_count)
        db.share_instance_access_update.assert_has_calls([
            mock.call(self.context, mock.ANY, share['instance']['id'],
                      expected_updates),
        ] * changes_allowed)

    def test_get_and_update_access_rule_just_get(self):
        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
        expected_rule = db_utils.create_access(share_id=share['id'])
        self.mock_object(db, 'share_instance_access_update')

        actual_rule = (
            self.access_helper.get_and_update_share_instance_access_rule(
                self.context, expected_rule['id'],
                share_instance_id=share['instance']['id'])
        )

        self.assertEqual(expected_rule['id'], actual_rule['access_id'])
        self.assertFalse(db.share_instance_access_update.called)

    @ddt.data(constants.ACCESS_STATE_APPLYING,
              constants.ACCESS_STATE_DENYING,
              constants.ACCESS_STATE_ACTIVE,
              constants.ACCESS_STATE_QUEUED_TO_APPLY)
    def test_get_and_update_access_rule_updates_conditionally_changed(
            self, initial_state):
        mock_debug_log = self.mock_object(access.LOG, 'debug')
        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
        rule = db_utils.create_access(share_id=share['id'],
                                      state=initial_state)
        self.mock_object(db, 'share_instance_access_update', mock.Mock(
            side_effect=db.share_instance_access_update))
        updates = {
            'access_key': 'renfrow2stars'
        }
        conditionally_change = {
            constants.ACCESS_STATE_APPLYING:
                constants.ACCESS_STATE_QUEUED_TO_DENY,
            constants.ACCESS_STATE_DENYING:
                constants.ACCESS_STATE_QUEUED_TO_DENY,
        }

        actual_rule = (
            self.access_helper.get_and_update_share_instance_access_rule(
                self.context, rule['id'], updates=updates,
                share_instance_id=share['instance']['id'],
                conditionally_change=conditionally_change)
        )
        self.assertEqual(rule['id'], actual_rule['access_id'])
        if 'ing' in initial_state:
            self.assertEqual(constants.ACCESS_STATE_QUEUED_TO_DENY,
                             actual_rule['state'])
            self.assertFalse(mock_debug_log.called)
        else:
            self.assertEqual(initial_state, actual_rule['state'])
            mock_debug_log.assert_called_once()


@ddt.ddt
class ShareInstanceAccessTestCase(test.TestCase):
    def setUp(self):
        super(ShareInstanceAccessTestCase, self).setUp()
        self.driver = self.mock_class("manila.share.driver.ShareDriver",
                                      mock.Mock())
        self.access_helper = access.ShareInstanceAccess(db, self.driver)
        self.context = context.RequestContext('fake_user', 'fake_project')

    @ddt.data(constants.ACCESS_STATE_APPLYING, constants.ACCESS_STATE_DENYING)
    def test_update_access_rules_an_update_is_in_progress(self, initial_state):
        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
        share_instance = share['instance']
        db_utils.create_access(share_id=share['id'], state=initial_state)
        mock_debug_log = self.mock_object(access.LOG, 'debug')
        self.mock_object(self.access_helper, '_update_access_rules')
        get_and_update_call = self.mock_object(
            self.access_helper, 'get_and_update_share_instance_access_rules',
            mock.Mock(side_effect=self.access_helper.
                      get_and_update_share_instance_access_rules))

        retval = self.access_helper.update_access_rules(
            self.context, share_instance['id'])

        expected_filters = {
            'state': (constants.ACCESS_STATE_APPLYING,
                      constants.ACCESS_STATE_DENYING),
        }
        self.assertIsNone(retval)
        mock_debug_log.assert_called_once()
        get_and_update_call.assert_called_once_with(
            self.context, filters=expected_filters,
            share_instance_id=share_instance['id'])
        self.assertFalse(self.access_helper._update_access_rules.called)

    def test_update_access_rules_nothing_to_update(self):
        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
        share_instance = share['instance']
        db_utils.create_access(share_id=share['id'],
                               state=constants.STATUS_ACTIVE)
        mock_debug_log = self.mock_object(access.LOG, 'debug')
        self.mock_object(self.access_helper, '_update_access_rules')
        get_and_update_call = self.mock_object(
            self.access_helper, 'get_and_update_share_instance_access_rules',
            mock.Mock(side_effect=self.access_helper.
                      get_and_update_share_instance_access_rules))

        retval = self.access_helper.update_access_rules(
            self.context, share_instance['id'])

        expected_rule_filter_1 = {
            'state': (constants.ACCESS_STATE_APPLYING,
                      constants.ACCESS_STATE_DENYING),
        }
        expected_rule_filter_2 = {
            'state': (constants.ACCESS_STATE_QUEUED_TO_APPLY,
                      constants.ACCESS_STATE_QUEUED_TO_DENY),
        }
        expected_conditionally_change = {
            constants.ACCESS_STATE_QUEUED_TO_APPLY:
                constants.ACCESS_STATE_APPLYING,
            constants.ACCESS_STATE_QUEUED_TO_DENY:
                constants.ACCESS_STATE_DENYING,
        }
        self.assertIsNone(retval)
        mock_debug_log.assert_called_once()
        get_and_update_call.assert_has_calls(
            [
                mock.call(self.context, filters=expected_rule_filter_1,
                          share_instance_id=share_instance['id']),
                mock.call(self.context, filters=expected_rule_filter_2,
                          share_instance_id=share_instance['id'],
                          conditionally_change=expected_conditionally_change),
            ])
        self.assertFalse(self.access_helper._update_access_rules.called)

    @ddt.data(True, False)
    def test_update_access_rules_delete_all_rules(self, delete_all_rules):
        share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
        share_instance = share['instance']
        db_utils.create_access(
            share_id=share['id'], state=constants.STATUS_ACTIVE)
        db_utils.create_access(
            share_id=share['id'], state=constants.ACCESS_STATE_QUEUED_TO_APPLY)
        db_utils.create_access(
            share_id=share['id'], state=constants.ACCESS_STATE_QUEUED_TO_DENY)
        mock_debug_log = self.mock_object(access.LOG, 'debug')
        self.mock_object(self.access_helper, '_update_access_rules')
        get_and_update_call = self.mock_object(
            self.access_helper, 'get_and_update_share_instance_access_rules',
            mock.Mock(side_effect=self.access_helper.
                      get_and_update_share_instance_access_rules))

        retval = self.access_helper.update_access_rules(
            self.context, share_instance['id'],
            delete_all_rules=delete_all_rules)

        expected_rule_filter_1 = {
            'state': (constants.ACCESS_STATE_APPLYING,
                      constants.ACCESS_STATE_DENYING),
        }
        expected_rule_filter_2 = {
            'state': (constants.ACCESS_STATE_QUEUED_TO_APPLY,
                      constants.ACCESS_STATE_QUEUED_TO_DENY),
        }
        expected_conditionally_change = {
            constants.ACCESS_STATE_QUEUED_TO_APPLY:
                constants.ACCESS_STATE_APPLYING,
            constants.ACCESS_STATE_QUEUED_TO_DENY:
                constants.ACCESS_STATE_DENYING,
        }
        expected_get_and_update_calls = []
        if delete_all_rules:
            deny_all_updates = {
                'state': constants.ACCESS_STATE_QUEUED_TO_DENY,
            }
            expected_get_and_update_calls = [
                mock.call(self.context, updates=deny_all_updates,
                          share_instance_id=share_instance['id']),
            ]
        expected_get_and_update_calls.extend([
            mock.call(self.context, filters=expected_rule_filter_1,
                      share_instance_id=share_instance['id']),
            mock.call(self.context, filters=expected_rule_filter_2,
                      share_instance_id=share_instance['id'],
                      conditionally_change=expected_conditionally_change),
        ])

        self.assertIsNone(retval)
        mock_debug_log.assert_called_once()
        get_and_update_call.assert_has_calls(expected_get_and_update_calls)
        self.access_helper._update_access_rules.assert_called_once_with(
            self.context, share_instance['id'], share_server=None)

    @ddt.data(*itertools.product(
        (True, False), (constants.ACCESS_STATE_ERROR,
                        constants.ACCESS_STATE_ACTIVE)))
    @ddt.unpack
    def test__update_access_rules_with_driver_updates(
            self, driver_returns_updates, access_state):
        expected_access_rules_status = (
            constants.STATUS_ACTIVE
            if access_state == constants.ACCESS_STATE_ACTIVE
            else constants.SHARE_INSTANCE_RULES_ERROR
        )
        share = db_utils.create_share(
            status=constants.STATUS_AVAILABLE,
            access_rules_status=expected_access_rules_status)
        share_instance_id = share['instance']['id']
        rule_1 = db_utils.create_access(
            share_id=share['id'], state=access_state)
        rule_1 = db.share_instance_access_get(
            self.context, rule_1['id'], share_instance_id)
        rule_2 = db_utils.create_access(
            share_id=share['id'], state=constants.ACCESS_STATE_APPLYING)
        rule_2 = db.share_instance_access_get(
            self.context, rule_2['id'], share_instance_id)
        rule_3 = db_utils.create_access(
            share_id=share['id'], state=constants.ACCESS_STATE_DENYING)
        rule_3 = db.share_instance_access_get(
            self.context, rule_3['id'], share_instance_id)
        if driver_returns_updates:
            driver_rule_updates = {
                rule_3['access_id']: {'access_key': 'alic3h4sAcc355'},
                rule_2['access_id']: {'state': access_state}
            }
        else:
            driver_rule_updates = None

        shr_instance_access_rules_status_update_call = self.mock_object(
            self.access_helper,
            'get_and_update_share_instance_access_rules_status',
            mock.Mock(side_effect=self.access_helper.
                      get_and_update_share_instance_access_rules_status))
        all_access_rules_update_call = self.mock_object(
            self.access_helper, 'get_and_update_share_instance_access_rules',
            mock.Mock(side_effect=self.access_helper.
                      get_and_update_share_instance_access_rules))
        one_access_rule_update_call = self.mock_object(
            self.access_helper, 'get_and_update_share_instance_access_rule',
            mock.Mock(side_effect=self.access_helper.
                      get_and_update_share_instance_access_rule))

        driver_call = self.mock_object(
            self.access_helper.driver, 'update_access',
            mock.Mock(return_value=driver_rule_updates))
        self.mock_object(self.access_helper, '_check_needs_refresh',
                         mock.Mock(return_value=False))

        retval = self.access_helper._update_access_rules(
            self.context, share_instance_id, share_server='fake_server')

        # Expected Values:
        if access_state != constants.ACCESS_STATE_ERROR:
            expected_rules_to_be_on_share = [r['id'] for r in (rule_1, rule_2)]
        else:
            expected_rules_to_be_on_share = [rule_2['id']]

        expected_filters_1 = {
            'state': (constants.ACCESS_STATE_APPLYING,
                      constants.ACCESS_STATE_ACTIVE,
                      constants.ACCESS_STATE_DENYING),
        }
        expected_filters_2 = {'state': constants.STATUS_ERROR}
        expected_get_and_update_calls = [
            mock.call(self.context, filters=expected_filters_1,
                      share_instance_id=share_instance_id),
            mock.call(self.context, filters=expected_filters_2,
                      share_instance_id=share_instance_id),
        ]
        expected_access_rules_status_change_cond1 = {
            constants.STATUS_ACTIVE: constants.SHARE_INSTANCE_RULES_SYNCING,
        }
        if access_state == constants.SHARE_INSTANCE_RULES_ERROR:
            expected_access_rules_status_change_cond2 = {
                constants.SHARE_INSTANCE_RULES_SYNCING:
                    constants.SHARE_INSTANCE_RULES_ERROR,
            }
        else:
            expected_access_rules_status_change_cond2 = {
                constants.SHARE_INSTANCE_RULES_SYNCING:
                    constants.STATUS_ACTIVE,
                constants.SHARE_INSTANCE_RULES_ERROR:
                    constants.STATUS_ACTIVE,
            }
        call_args = driver_call.call_args_list[0][0]
        call_kwargs = driver_call.call_args_list[0][1]
        access_rules_to_be_on_share = [r['id'] for r in call_args[2]]

        # Asserts
        self.assertIsNone(retval)
        self.assertEqual(share_instance_id, call_args[1]['id'])
        self.assertTrue(isinstance(access_rules_to_be_on_share, list))
        self.assertEqual(len(expected_rules_to_be_on_share),
                         len(access_rules_to_be_on_share))
        for pool in expected_rules_to_be_on_share:
            self.assertIn(pool, access_rules_to_be_on_share)
        self.assertEqual(1, len(call_kwargs['add_rules']))
        self.assertEqual(rule_2['id'], call_kwargs['add_rules'][0]['id'])
        self.assertEqual(1, len(call_kwargs['delete_rules']))
        self.assertEqual(rule_3['id'], call_kwargs['delete_rules'][0]['id'])
        self.assertEqual('fake_server', call_kwargs['share_server'])
        shr_instance_access_rules_status_update_call.assert_has_calls([
            mock.call(
                self.context, share_instance_id=share_instance_id,
                conditionally_change=expected_access_rules_status_change_cond1
            ),
            mock.call(
                self.context, share_instance_id=share_instance_id,
                conditionally_change=expected_access_rules_status_change_cond2
            ),
        ])

        if driver_returns_updates:
            expected_conditional_state_updates = {
                constants.ACCESS_STATE_APPLYING: access_state,
                constants.ACCESS_STATE_DENYING: access_state,
                constants.ACCESS_STATE_ACTIVE: access_state,
            }
            expected_access_rule_update_calls = [
                mock.call(
                    self.context, rule_3['access_id'],
                    updates={'access_key': 'alic3h4sAcc355'},
                    share_instance_id=share_instance_id,
                    conditionally_change={}),
                mock.call(
                    self.context, rule_2['access_id'],
                    updates=mock.ANY, share_instance_id=share_instance_id,
                    conditionally_change=expected_conditional_state_updates)
            ]
            one_access_rule_update_call.assert_has_calls(
                expected_access_rule_update_calls, any_order=True)
        else:
            self.assertFalse(one_access_rule_update_call.called)
            expected_conditionally_change = {
                constants.ACCESS_STATE_APPLYING: constants.ACCESS_STATE_ACTIVE,
            }
            expected_get_and_update_calls.append(
                mock.call(self.context, share_instance_id=share_instance_id,
                          conditionally_change=expected_conditionally_change))

        all_access_rules_update_call.assert_has_calls(
            expected_get_and_update_calls, any_order=True)

        share_instance = db.share_instance_get(
            self.context, share_instance_id)
        self.assertEqual(expected_access_rules_status,
                         share_instance['access_rules_status'])

    @ddt.data(True, False)
    def test__update_access_rules_recursive_driver_exception(self, drv_exc):
        other = access.ShareInstanceAccess(db, None)
        share = db_utils.create_share(
            status=constants.STATUS_AVAILABLE,
            access_rules_status=constants.SHARE_INSTANCE_RULES_SYNCING)
        share_instance_id = share['instance']['id']
        rule_4 = []
        get_and_update_count = [1]
        drv_count = [1]

        def _get_and_update_side_effect(*args, **kwargs):
            # The third call to this method needs to create a new access rule
            mtd = other.get_and_update_share_instance_access_rules
            if get_and_update_count[0] == 3:
                rule_4.append(
                    db_utils.create_access(
                        state=constants.ACCESS_STATE_QUEUED_TO_APPLY,
                        share_id=share['id']))
            get_and_update_count[0] += 1
            return mtd(*args, **kwargs)

        def _driver_side_effect(*args, **kwargs):
            if drv_exc and drv_count[0] == 2:
                raise exception.ManilaException('fake')
            drv_count[0] += 1

        rule_kwargs = {'share_id': share['id'], 'access_level': 'rw'}
        rule_1 = db_utils.create_access(state=constants.ACCESS_STATE_APPLYING,
                                        **rule_kwargs)
        rule_2 = db_utils.create_access(state=constants.ACCESS_STATE_ACTIVE,
                                        **rule_kwargs)
        rule_3 = db_utils.create_access(state=constants.ACCESS_STATE_DENYING,
                                        **rule_kwargs)

        self.mock_object(self.access_helper,
                         'get_and_update_share_instance_access_rules',
                         mock.Mock(side_effect=_get_and_update_side_effect))
        self.mock_object(self.access_helper.driver, 'update_access',
                         mock.Mock(side_effect=_driver_side_effect))

        if drv_exc:
            self.assertRaises(exception.ManilaException,
                              self.access_helper._update_access_rules,
                              self.context, share_instance_id)
        else:
            retval = self.access_helper._update_access_rules(self.context,
                                                             share_instance_id)
            self.assertIsNone(retval)

        expected_filters_1 = {
            'state': (constants.ACCESS_STATE_APPLYING,
                      constants.ACCESS_STATE_ACTIVE,
                      constants.ACCESS_STATE_DENYING),
        }
        conditionally_change_2 = {
            constants.ACCESS_STATE_APPLYING: constants.ACCESS_STATE_ACTIVE,
        }
        expected_filters_3 = {
            'state': (constants.ACCESS_STATE_QUEUED_TO_APPLY,
                      constants.ACCESS_STATE_QUEUED_TO_DENY),
        }
        expected_conditionally_change_3 = {
            constants.ACCESS_STATE_QUEUED_TO_APPLY:
                constants.ACCESS_STATE_APPLYING,
            constants.ACCESS_STATE_QUEUED_TO_DENY:
                constants.ACCESS_STATE_DENYING,
        }
        expected_conditionally_change_4 = {
            constants.ACCESS_STATE_APPLYING: constants.ACCESS_STATE_ERROR,
            constants.ACCESS_STATE_DENYING: constants.ACCESS_STATE_ERROR,
        }
        expected_get_and_update_calls = [
            mock.call(self.context, filters=expected_filters_1,
                      share_instance_id=share_instance_id),
            mock.call(self.context, share_instance_id=share_instance_id,
                      conditionally_change=conditionally_change_2),
            mock.call(self.context, filters=expected_filters_3,
                      share_instance_id=share_instance_id,
                      conditionally_change=expected_conditionally_change_3),
            mock.call(self.context, filters=expected_filters_1,
                      share_instance_id=share_instance_id),
        ]

        if drv_exc:
            expected_get_and_update_calls.append(
                mock.call(
                    self.context, share_instance_id=share_instance_id,
                    conditionally_change=expected_conditionally_change_4))
        else:
            expected_get_and_update_calls.append(
                mock.call(self.context, share_instance_id=share_instance_id,
                          conditionally_change=conditionally_change_2))

        # Verify rule changes:
        # 'denying' rule must not exist
        self.assertRaises(exception.NotFound,
                          db.share_access_get,
                          self.context, rule_3['id'])
        # 'applying' rule must be set to 'active'
        rules_that_must_be_active = (rule_1, rule_2)
        if not drv_exc:
            rules_that_must_be_active += (rule_4[0], )
        for rule in rules_that_must_be_active:
            rule = db.share_access_get(self.context, rule['id'])
        self.assertEqual(constants.ACCESS_STATE_ACTIVE,
                         rule['state'])
        # access_rules_status must be as expected
        expected_access_rules_status = (
            constants.SHARE_INSTANCE_RULES_ERROR if drv_exc
            else constants.STATUS_ACTIVE)
        share_instance = db.share_instance_get(self.context, share_instance_id)
        self.assertEqual(
            expected_access_rules_status,
            share_instance['access_rules_status'])

    def test__update_access_rules_for_migration(self):
        share = db_utils.create_share()
        instance = db_utils.create_share_instance(
            status=constants.STATUS_MIGRATING,
            access_rules_status=constants.STATUS_ACTIVE,
            cast_rules_to_readonly=True,
            share_id=share['id'])
        rule_kwargs = {'share_id': share['id'], 'access_level': 'rw'}
        rule_1 = db_utils.create_access(
            state=constants.ACCESS_STATE_ACTIVE, **rule_kwargs)
        rule_1 = db.share_instance_access_get(
            self.context, rule_1['id'], instance['id'])
        rule_2 = db_utils.create_access(
            state=constants.ACCESS_STATE_APPLYING, share_id=share['id'],
            access_level='ro')
        rule_2 = db.share_instance_access_get(
            self.context, rule_2['id'], instance['id'])

        driver_call = self.mock_object(
            self.access_helper.driver, 'update_access',
            mock.Mock(return_value=None))
        self.mock_object(self.access_helper, '_check_needs_refresh',
                         mock.Mock(return_value=False))

        retval = self.access_helper._update_access_rules(
            self.context, instance['id'], share_server='fake_server')

        call_args = driver_call.call_args_list[0][0]
        call_kwargs = driver_call.call_args_list[0][1]
        access_rules_to_be_on_share = [r['id'] for r in call_args[2]]
        access_levels = [r['access_level'] for r in call_args[2]]
        expected_rules_to_be_on_share = ([rule_1['id'], rule_2['id']])

        self.assertIsNone(retval)
        self.assertEqual(instance['id'], call_args[1]['id'])
        self.assertTrue(isinstance(access_rules_to_be_on_share, list))
        self.assertEqual(len(expected_rules_to_be_on_share),
                         len(access_rules_to_be_on_share))
        for pool in expected_rules_to_be_on_share:
            self.assertIn(pool, access_rules_to_be_on_share)
        self.assertEqual(['ro'] * len(expected_rules_to_be_on_share),
                         access_levels)
        self.assertEqual(0, len(call_kwargs['add_rules']))
        self.assertEqual(0, len(call_kwargs['delete_rules']))
        self.assertEqual('fake_server', call_kwargs['share_server'])

    @ddt.data(True, False)
    def test__check_needs_refresh(self, expected_needs_refresh):
        states = (
            [constants.ACCESS_STATE_QUEUED_TO_DENY,
             constants.ACCESS_STATE_QUEUED_TO_APPLY] if expected_needs_refresh
            else [constants.ACCESS_STATE_ACTIVE]
        )
        share = db_utils.create_share(
            status=constants.STATUS_AVAILABLE,
            access_rules_status=constants.SHARE_INSTANCE_RULES_SYNCING)
        share_instance_id = share['instance']['id']
        rule_kwargs = {'share_id': share['id'], 'access_level': 'rw'}
        rule_1 = db_utils.create_access(state=states[0], **rule_kwargs)
        db_utils.create_access(state=constants.ACCESS_STATE_ACTIVE,
                               **rule_kwargs)
        db_utils.create_access(state=constants.ACCESS_STATE_DENYING,
                               **rule_kwargs)
        rule_4 = db_utils.create_access(state=states[-1], **rule_kwargs)

        get_and_update_call = self.mock_object(
            self.access_helper, 'get_and_update_share_instance_access_rules',
            mock.Mock(side_effect=self.access_helper.
                      get_and_update_share_instance_access_rules))

        needs_refresh = self.access_helper._check_needs_refresh(
            self.context, share_instance_id)

        expected_filter = {
            'state': (constants.ACCESS_STATE_QUEUED_TO_APPLY,
                      constants.ACCESS_STATE_QUEUED_TO_DENY),
        }
        expected_conditionally_change = {
            constants.ACCESS_STATE_QUEUED_TO_APPLY:
                constants.ACCESS_STATE_APPLYING,
            constants.ACCESS_STATE_QUEUED_TO_DENY:
                constants.ACCESS_STATE_DENYING,
        }

        self.assertEqual(expected_needs_refresh, needs_refresh)
        get_and_update_call.assert_called_once_with(
            self.context, filters=expected_filter,
            share_instance_id=share_instance_id,
            conditionally_change=expected_conditionally_change)

        rule_1 = db.share_instance_access_get(
            self.context, rule_1['id'], share_instance_id)
        rule_4 = db.share_instance_access_get(
            self.context, rule_4['id'], share_instance_id)

        if expected_needs_refresh:
            self.assertEqual(constants.ACCESS_STATE_DENYING, rule_1['state'])
            self.assertEqual(constants.ACCESS_STATE_APPLYING, rule_4['state'])
        else:
            self.assertEqual(states[0], rule_1['state'])
            self.assertEqual(states[-1], rule_4['state'])

    @ddt.data(('nfs', True, False), ('nfs', False, True),
              ('cifs', True, False), ('cifs', False, False),
              ('cephx', True, False), ('cephx', False, False))
    @ddt.unpack
    def test__update_rules_through_share_driver(self, proto,
                                                enable_ipv6, filtered):
        self.driver.ipv6_implemented = enable_ipv6
        share_instance = {'share_proto': proto}
        pass_rules, fail_rules = self._get_pass_rules_and_fail_rules()
        pass_add_rules, fail_add_rules = self._get_pass_rules_and_fail_rules()
        pass_delete_rules, fail_delete_rules = (
            self._get_pass_rules_and_fail_rules())
        test_rules = pass_rules + fail_rules
        test_add_rules = pass_add_rules + fail_add_rules
        test_delete_rules = pass_delete_rules + fail_delete_rules

        fake_expect_driver_update_rules = pass_rules
        update_access_call = self.mock_object(
            self.access_helper.driver, 'update_access',
            mock.Mock(return_value=pass_rules))
        driver_update_rules = (
            self.access_helper._update_rules_through_share_driver(
                self.context, share_instance=share_instance,
                access_rules_to_be_on_share=test_rules,
                add_rules=test_add_rules,
                delete_rules=test_delete_rules,
                rules_to_be_removed_from_db=test_rules,
                share_server=None))

        if filtered:
            update_access_call.assert_called_once_with(
                self.context, share_instance,
                pass_rules, add_rules=pass_add_rules,
                delete_rules=pass_delete_rules, share_server=None)
        else:
            update_access_call.assert_called_once_with(
                self.context, share_instance, test_rules,
                add_rules=test_add_rules, delete_rules=test_delete_rules,
                share_server=None)
        self.assertEqual(fake_expect_driver_update_rules, driver_update_rules)

    def _get_pass_rules_and_fail_rules(self):
        random_value = six.text_type(random.randint(10, 32))
        pass_rules = [
            {
                'access_type': 'ip',
                'access_to': '1.1.1.' + random_value,
            },
            {
                'access_type': 'ip',
                'access_to': '1.1.%s.0/24' % random_value,
            },
            {
                'access_type': 'user',
                'access_to': 'fake_user' + random_value,
            },
        ]
        fail_rules = [
            {
                'access_type': 'ip',
                'access_to': '1001::' + random_value,
            },
            {
                'access_type': 'ip',
                'access_to': '%s::/64' % random_value,
            },
        ]
        return pass_rules, fail_rules
