You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
9.1 KiB
Python
201 lines
9.1 KiB
Python
# 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 mock
|
|
from neutron_lib.callbacks import events
|
|
from neutron_lib.callbacks import registry
|
|
from neutron_lib import context
|
|
|
|
from neutron.agent import resource_cache
|
|
from neutron.api.rpc.callbacks import events as events_rpc
|
|
from neutron.tests import base
|
|
|
|
|
|
class OVOLikeThing(object):
|
|
def __init__(self, id, revision_number=10, **kwargs):
|
|
self.id = id
|
|
self.fields = ['id', 'revision_number']
|
|
self.revision_number = revision_number
|
|
for k, v in kwargs.items():
|
|
self.fields.append(k)
|
|
setattr(self, k, v)
|
|
|
|
def to_dict(self):
|
|
return {f: getattr(self, f) for f in self.fields}
|
|
|
|
def get(self, k):
|
|
return getattr(self, k, None)
|
|
|
|
|
|
class RemoteResourceCacheTestCase(base.BaseTestCase):
|
|
def setUp(self):
|
|
super(RemoteResourceCacheTestCase, self).setUp()
|
|
rtypes = ['duck', 'goose']
|
|
self.goose = OVOLikeThing(1)
|
|
self.duck = OVOLikeThing(2)
|
|
self.ctx = context.get_admin_context()
|
|
self.rcache = resource_cache.RemoteResourceCache(rtypes)
|
|
self._pullmock = mock.patch.object(self.rcache, '_puller').start()
|
|
|
|
def test_get_resource_by_id(self):
|
|
self.rcache.record_resource_update(self.ctx, 'goose', self.goose)
|
|
self.assertEqual(self.goose,
|
|
self.rcache.get_resource_by_id('goose', 1))
|
|
self.assertIsNone(self.rcache.get_resource_by_id('goose', 2))
|
|
|
|
def test__flood_cache_for_query_pulls_once(self):
|
|
resources = [OVOLikeThing(66), OVOLikeThing(67)]
|
|
received_kw = []
|
|
receiver = lambda *a, **k: received_kw.append(k)
|
|
registry.subscribe(receiver, 'goose', events.AFTER_UPDATE)
|
|
|
|
self._pullmock.bulk_pull.side_effect = [
|
|
resources,
|
|
[resources[0]],
|
|
[resources[1]],
|
|
[resources[1]]
|
|
]
|
|
|
|
self.rcache._flood_cache_for_query('goose', id=(66, 67),
|
|
name=('a', 'b'))
|
|
self._pullmock.bulk_pull.assert_called_once_with(
|
|
mock.ANY, 'goose',
|
|
filter_kwargs={'id': (66, 67), 'name': ('a', 'b')})
|
|
|
|
self._pullmock.bulk_pull.reset_mock()
|
|
self.rcache._flood_cache_for_query('goose', id=(66, ), name=('a', ))
|
|
self.assertFalse(self._pullmock.called)
|
|
self.rcache._flood_cache_for_query('goose', id=(67, ), name=('b', ))
|
|
self.assertFalse(self._pullmock.called)
|
|
|
|
# querying by just ID should trigger a new call since ID+name is a more
|
|
# specific query
|
|
self.rcache._flood_cache_for_query('goose', id=(67, ))
|
|
self._pullmock.bulk_pull.assert_called_once_with(
|
|
mock.ANY, 'goose', filter_kwargs={'id': (67, )})
|
|
|
|
self.assertItemsEqual(
|
|
resources, [rec['updated'] for rec in received_kw])
|
|
|
|
def test_bulk_pull_doesnt_wipe_out_newer_data(self):
|
|
self.rcache.record_resource_update(
|
|
self.ctx, 'goose', OVOLikeThing(1, revision_number=5))
|
|
updated = OVOLikeThing(1)
|
|
updated.revision_number = 1 # older revision number
|
|
self._pullmock.bulk_pull.return_value = [updated]
|
|
self.rcache._flood_cache_for_query('goose', id=(1,),)
|
|
self.assertEqual(
|
|
5, self.rcache.get_resource_by_id('goose', 1).revision_number)
|
|
|
|
def test_get_resources(self):
|
|
geese = [OVOLikeThing(3, size='large'), OVOLikeThing(5, size='medium'),
|
|
OVOLikeThing(4, size='large'), OVOLikeThing(6, size='small')]
|
|
for goose in geese:
|
|
self.rcache.record_resource_update(self.ctx, 'goose', goose)
|
|
is_large = {'size': ('large', )}
|
|
is_small = {'size': ('small', )}
|
|
self.assertItemsEqual([geese[0], geese[2]],
|
|
self.rcache.get_resources('goose', is_large))
|
|
self.assertItemsEqual([geese[3]],
|
|
self.rcache.get_resources('goose', is_small))
|
|
|
|
def test_match_resources_with_func(self):
|
|
geese = [OVOLikeThing(3, size='large'), OVOLikeThing(5, size='medium'),
|
|
OVOLikeThing(4, size='xlarge'), OVOLikeThing(6, size='small')]
|
|
for goose in geese:
|
|
self.rcache.record_resource_update(self.ctx, 'goose', goose)
|
|
has_large = lambda o: 'large' in o.size
|
|
self.assertItemsEqual([geese[0], geese[2]],
|
|
self.rcache.match_resources_with_func('goose',
|
|
has_large))
|
|
|
|
def test__is_stale(self):
|
|
goose = OVOLikeThing(3, size='large')
|
|
self.rcache.record_resource_update(self.ctx, 'goose', goose)
|
|
# same revision id is not considered stale
|
|
updated = OVOLikeThing(3, size='large')
|
|
self.assertFalse(self.rcache._is_stale('goose', updated))
|
|
updated.revision_number = 0
|
|
self.assertTrue(self.rcache._is_stale('goose', updated))
|
|
updated.revision_number = 200
|
|
self.assertFalse(self.rcache._is_stale('goose', updated))
|
|
# once deleted, all updates are stale
|
|
self.rcache.record_resource_delete(self.ctx, 'goose', 3)
|
|
self.assertTrue(self.rcache._is_stale('goose', updated))
|
|
|
|
def test_record_resource_update(self):
|
|
received_kw = []
|
|
receiver = lambda *a, **k: received_kw.append(k)
|
|
registry.subscribe(receiver, 'goose', events.AFTER_UPDATE)
|
|
self.rcache.record_resource_update(self.ctx, 'goose',
|
|
OVOLikeThing(3, size='large'))
|
|
self.assertEqual(1, len(received_kw))
|
|
self.assertIsNone(received_kw[0]['existing'])
|
|
# another update with no changed fields results in no callback
|
|
self.rcache.record_resource_update(self.ctx, 'goose',
|
|
OVOLikeThing(3, size='large',
|
|
revision_number=100))
|
|
self.assertEqual(1, len(received_kw))
|
|
self.rcache.record_resource_update(self.ctx, 'goose',
|
|
OVOLikeThing(3, size='small',
|
|
revision_number=101))
|
|
self.assertEqual(2, len(received_kw))
|
|
self.assertEqual('large', received_kw[1]['existing'].size)
|
|
self.assertEqual('small', received_kw[1]['updated'].size)
|
|
self.assertEqual(set(['size']), received_kw[1]['changed_fields'])
|
|
|
|
def test_record_resource_delete(self):
|
|
received_kw = []
|
|
receiver = lambda *a, **k: received_kw.append(k)
|
|
registry.subscribe(receiver, 'goose', events.AFTER_DELETE)
|
|
self.rcache.record_resource_update(self.ctx, 'goose',
|
|
OVOLikeThing(3, size='large'))
|
|
self.rcache.record_resource_delete(self.ctx, 'goose', 3)
|
|
self.assertEqual(1, len(received_kw))
|
|
self.assertEqual(3, received_kw[0]['existing'].id)
|
|
self.assertEqual(3, received_kw[0]['resource_id'])
|
|
# deletes of non-existing cache items are still honored
|
|
self.rcache.record_resource_delete(self.ctx, 'goose', 4)
|
|
self.assertEqual(2, len(received_kw))
|
|
self.assertIsNone(received_kw[1]['existing'])
|
|
self.assertEqual(4, received_kw[1]['resource_id'])
|
|
|
|
def test_record_resource_delete_ignores_dups(self):
|
|
received_kw = []
|
|
receiver = lambda *a, **k: received_kw.append(k)
|
|
registry.subscribe(receiver, 'goose', events.AFTER_DELETE)
|
|
self.rcache.record_resource_delete(self.ctx, 'goose', 3)
|
|
self.assertEqual(1, len(received_kw))
|
|
self.rcache.record_resource_delete(self.ctx, 'goose', 4)
|
|
self.assertEqual(2, len(received_kw))
|
|
self.rcache.record_resource_delete(self.ctx, 'goose', 3)
|
|
self.assertEqual(2, len(received_kw))
|
|
|
|
def test_resource_change_handler(self):
|
|
with mock.patch.object(resource_cache.RemoteResourceWatcher,
|
|
'_init_rpc_listeners'):
|
|
watch = resource_cache.RemoteResourceWatcher(self.rcache)
|
|
geese = [OVOLikeThing(3, size='large'), OVOLikeThing(5, size='medium'),
|
|
OVOLikeThing(4, size='large'), OVOLikeThing(6, size='small')]
|
|
watch.resource_change_handler(self.ctx, 'goose', geese,
|
|
events_rpc.UPDATED)
|
|
for goose in geese:
|
|
self.assertEqual(goose,
|
|
self.rcache.get_resource_by_id('goose', goose.id))
|
|
watch.resource_change_handler(self.ctx, 'goose', geese,
|
|
events_rpc.DELETED)
|
|
for goose in geese:
|
|
self.assertIsNone(
|
|
self.rcache.get_resource_by_id('goose', goose.id))
|