Browse Source

Base mechanism for recovery

This patch introduces default implmentation of get_resource method
to ResourceBaseDriver class to allow recovery mechanism to fetch a
resource based on id. If a driver wants to provide its own
implementation then introduced method has to be overridden.

Change-Id: I951d05783ccbe1bc873a76511d254f90e71a4914
Partial-Bug: #1713697
tags/12.0.0.0rc1
Rajiv Kumar 3 years ago
committed by Rajiv Kumar
parent
commit
64f0692d68
7 changed files with 142 additions and 55 deletions
  1. +1
    -1
      networking_odl/common/exceptions.py
  2. +14
    -6
      networking_odl/journal/base_driver.py
  3. +12
    -0
      networking_odl/journal/recovery.py
  4. +49
    -0
      networking_odl/tests/unit/journal/helper.py
  5. +31
    -38
      networking_odl/tests/unit/journal/test_base_driver.py
  6. +10
    -10
      networking_odl/tests/unit/journal/test_full_sync.py
  7. +25
    -0
      networking_odl/tests/unit/journal/test_recovery.py

+ 1
- 1
networking_odl/common/exceptions.py View File

@@ -30,7 +30,7 @@ class FullSyncError(NetworkingODLException):

class UnsupportedResourceType(NetworkingODLException):
"""An exception for unsupported resource for full sync and recovery"""
pass
message = _("unsupported resource type: %(resource)s")


class PluginMethodNotFound(NetworkingODLException, AttributeError):


+ 14
- 6
networking_odl/journal/base_driver.py View File

@@ -54,20 +54,28 @@ class ResourceBaseDriver(object):
for resource in self.RESOURCES:
ALL_RESOURCES[resource] = self

def _get_resource_getter(self, method_suffix):
method_name = "get_%s" % method_suffix
try:
return getattr(self.plugin, method_name)
except AttributeError:
raise exceptions.PluginMethodNotFound(plugin=self.plugin_type,
method=method_name)

def get_resources_for_full_sync(self, context, resource_type):
"""Provide all resources of type resource_type """
if resource_type not in self.RESOURCES:
raise exceptions.UnsupportedResourceType

method_name = 'get_%s' % self.RESOURCES[resource_type]
try:
resource_getter = getattr(self.plugin, method_name)
except AttributeError:
raise exceptions.PluginMethodNotFound(plugin=self.plugin_type,
method=method_name)
resource_getter = self._get_resource_getter(
self.RESOURCES[resource_type])

return resource_getter(context)

@property
def plugin(self):
return directory.get_plugin(self.plugin_type)

def get_resource_for_recovery(self, context, obj):
resource_getter = self._get_resource_getter(obj.object_type)
return resource_getter(context, obj.object_uuid)

+ 12
- 0
networking_odl/journal/recovery.py View File

@@ -23,7 +23,9 @@ from neutron.db import api as db_api
from networking_odl._i18n import _
from networking_odl.common import client
from networking_odl.common import constants as odl_const
from networking_odl.common import exceptions
from networking_odl.db import db
from networking_odl.journal import base_driver
from networking_odl.journal import full_sync
from networking_odl.journal import journal

@@ -57,6 +59,16 @@ def journal_recovery(context):
_handle_non_existing_resource(context, row)


def get_latest_resource(context, row):
try:
driver = base_driver.get_driver(row.object_type)
except exceptions.ResourceNotRegistered:
raise exceptions.UnsupportedResourceType(resource=row.object_type)

return driver.get_resource_for_recovery(context, row)


# TODO(rajivk): Remove this method once recovery is fully supported
def _get_latest_resource(context, row):
object_type = row.object_type



+ 49
- 0
networking_odl/tests/unit/journal/helper.py View File

@@ -0,0 +1,49 @@
# Copyright (c) 2017 OpenStack Foundation.
# 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.

from networking_odl.journal import base_driver


TEST_UUID = 'bd8db3a8-2b30-4083-a8b3-b3fd46401142'
TEST_PLUGIN = 'test_plugin'
TEST_RESOURCE1 = 'test_resource1'
TEST_RESOURCE2 = 'test_resource2'
TEST_RESOURCE1_SUFFIX = 'test_resource1s'
TEST_RESOURCE2_SUFFIX = 'test_resource2s'
INVALID_RESOURCE = 'invalid_resource'
INVALID_PLUGIN = 'invalid_plugin'
INVALID_METHOD = 'invalid_method_name'


class TestPlugin(object):
def get_test_resource1s(self, context):
return [{'id': 'test_id1'}, {'id': 'test_id2'}]

def get_test_resource2s(self, context):
return [{'id': 'test_id3'}, {'id': 'test_id4'}]

def get_test_resource1(self, context, id_):
return {'id': id_}


class TestDriver(base_driver.ResourceBaseDriver):
RESOURCES = {
TEST_RESOURCE1: TEST_RESOURCE1_SUFFIX,
TEST_RESOURCE2: TEST_RESOURCE2_SUFFIX
}
plugin_type = TEST_PLUGIN

def __init__(self):
super(TestDriver, self).__init__()

+ 31
- 38
networking_odl/tests/unit/journal/test_base_driver.py View File

@@ -12,73 +12,48 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from neutron_lib.plugins import directory

from networking_odl.common import constants
from networking_odl.common import exceptions
from networking_odl.db import db
from networking_odl.journal import base_driver
from networking_odl.tests.unit.journal import helper
from networking_odl.tests.unit import test_base_db

TEST_PLUGIN = 'test_plugin'
TEST_RESOURCE1 = 'test_resource1'
TEST_RESOURCE2 = 'test_resource2'
TEST_RESOURCE1_SUFFIX = 'test_resource1s'
TEST_RESOURCE2_SUFFIX = 'test_resource2s'
INVALID_RESOURCE = 'invalid_resource'
INVALID_PLUGIN = 'invalid_plugin'


class TestPlugin(object):
def get_test_resource1s(self, context):
return [{'id': 'test_id1'}, {'id': 'test_id2'}]

def get_test_resource2s(self, context):
return [{'id': 'test_id3'}, {'id': 'test_id4'}]


class TestDriver(base_driver.ResourceBaseDriver):
RESOURCES = {
TEST_RESOURCE1: TEST_RESOURCE1_SUFFIX,
TEST_RESOURCE2: TEST_RESOURCE2_SUFFIX
}
plugin_type = TEST_PLUGIN

def __init__(self):
super(TestDriver, self).__init__()


class BaseDriverTestCase(test_base_db.ODLBaseDbTestCase):
def setUp(self):
super(BaseDriverTestCase, self).setUp()
self.test_driver = TestDriver()
self.plugin = TestPlugin()
directory.add_plugin(TEST_PLUGIN, self.plugin)
self.addCleanup(directory.add_plugin, TEST_PLUGIN, None)
self.test_driver = helper.TestDriver()
self.plugin = helper.TestPlugin()
directory.add_plugin(helper.TEST_PLUGIN, self.plugin)
self.addCleanup(directory.add_plugin, helper.TEST_PLUGIN, None)

def test_get_resource_driver(self):
for resource, resource_suffix in self.test_driver.RESOURCES.items():
driver = base_driver.get_driver(resource)
self.assertEqual(driver, self.test_driver)
self.assertEqual(driver.plugin_type, TEST_PLUGIN)
self.assertEqual(driver.plugin_type, helper.TEST_PLUGIN)
self.assertEqual(self.test_driver.RESOURCES.get(resource),
resource_suffix)

def non_existing_plugin_cleanup(self):
self.test_driver.plugin_type = TEST_PLUGIN
self.test_driver.plugin_type = helper.TEST_PLUGIN

def test_non_existing_plugin(self):
self.test_driver.plugin_type = INVALID_PLUGIN
self.test_driver.plugin_type = helper.INVALID_PLUGIN
self.addCleanup(self.non_existing_plugin_cleanup)
self.assertIsNone(self.test_driver.plugin)

def test_get_non_existing_resource_driver(self):
self.assertRaises(exceptions.ResourceNotRegistered,
base_driver.get_driver, INVALID_RESOURCE)
base_driver.get_driver, helper.INVALID_RESOURCE)

def test_get_resources_for_full_sync(self):
received_resources = self.test_driver.get_resources_for_full_sync(
self.db_context,
TEST_RESOURCE1)
helper.TEST_RESOURCE1)
resources = self.plugin.get_test_resource1s(self.db_context)
for resource in resources:
self.assertIn(resource, received_resources)
@@ -86,4 +61,22 @@ class BaseDriverTestCase(test_base_db.ODLBaseDbTestCase):
def test_get_non_existing_resources_for_full_sync(self):
self.assertRaises(exceptions.UnsupportedResourceType,
self.test_driver.get_resources_for_full_sync,
self.db_context, INVALID_RESOURCE)
self.db_context, helper.INVALID_RESOURCE)

def test_get_resource(self):
row = db.create_pending_row(self.db_session, helper.TEST_RESOURCE1,
helper.TEST_UUID, constants.ODL_CREATE,
{'id': helper.TEST_UUID})
resource = self.test_driver.get_resource_for_recovery(self.db_context,
row)

self.assertEqual(resource['id'], helper.TEST_UUID)

def test_get_unsupported_resource(self):
row = db.create_pending_row(self.db_session, helper.INVALID_RESOURCE,
helper.TEST_UUID, constants.ODL_CREATE,
{'id': helper.TEST_UUID})

self.assertRaises(exceptions.PluginMethodNotFound,
self.test_driver.get_resource_for_recovery,
self.db_context, row)

+ 10
- 10
networking_odl/tests/unit/journal/test_full_sync.py View File

@@ -40,7 +40,7 @@ from networking_odl.qos import qos_driver_v2 as qos_driver
from networking_odl.sfc.flowclassifier import sfc_flowclassifier_v2
from networking_odl.sfc import sfc_driver_v2 as sfc_driver
from networking_odl.tests import base
from networking_odl.tests.unit.journal import test_base_driver
from networking_odl.tests.unit.journal import helper
from networking_odl.tests.unit import test_base_db
from networking_odl.trunk import trunk_driver_v2 as trunk_driver

@@ -428,7 +428,7 @@ class FullSyncTestCase(test_base_db.ODLBaseDbTestCase):
self.assertEqual([], db.get_all_db_rows(self.db_session))

def _register_resources(self):
test_base_driver.TestDriver()
helper.TestDriver()
self.addCleanup(base_driver.ALL_RESOURCES.clear)

def add_plugin(self, plugin_type, plugin):
@@ -437,22 +437,22 @@ class FullSyncTestCase(test_base_db.ODLBaseDbTestCase):
def test_plugin_not_registered(self):
self._register_resources()
# NOTE(rajivk): workaround, as we don't have delete method for plugin
plugin = directory.get_plugin(test_base_driver.TEST_PLUGIN)
directory.add_plugin(test_base_driver.TEST_PLUGIN, None)
self.addCleanup(self.add_plugin, test_base_driver.TEST_PLUGIN, plugin)
plugin = directory.get_plugin(helper.TEST_PLUGIN)
directory.add_plugin(helper.TEST_PLUGIN, None)
self.addCleanup(self.add_plugin, helper.TEST_PLUGIN, plugin)
self.assertRaises(exceptions.PluginMethodNotFound,
full_sync.sync_resources,
self.db_context,
test_base_driver.TEST_RESOURCE1)
helper.TEST_RESOURCE1)
self.assertEqual([], db.get_all_db_rows(self.db_session))

def test_sync_resources(self):
self._register_resources()
plugin = test_base_driver.TestPlugin()
self.add_plugin(test_base_driver.TEST_PLUGIN, plugin)
plugin = helper.TestPlugin()
self.add_plugin(helper.TEST_PLUGIN, plugin)
resources = plugin.get_test_resource1s(self.db_context)
full_sync.sync_resources(self.db_context,
test_base_driver.TEST_RESOURCE1)
helper.TEST_RESOURCE1)
entries = [entry.data for entry in db.get_all_db_rows(self.db_session)]
for resource in resources:
self.assertIn(resource, entries)
@@ -463,7 +463,7 @@ class FullSyncTestCase(test_base_db.ODLBaseDbTestCase):
def test_get_resources_failed(self, mock_get_resources):
self._register_resources()
mock_get_resources.side_effect = exceptions.UnsupportedResourceType()
resource_name = test_base_driver.TEST_RESOURCE1
resource_name = helper.TEST_RESOURCE1
self.assertRaises(exceptions.UnsupportedResourceType,
full_sync.sync_resources, self.db_context,
resource_name)


+ 25
- 0
networking_odl/tests/unit/journal/test_recovery.py View File

@@ -21,12 +21,14 @@ from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory

from networking_odl.common import constants as odl_const
from networking_odl.common import exceptions
from networking_odl.db import db
from networking_odl.journal import full_sync
from networking_odl.journal import recovery
from networking_odl.l3 import l3_odl_v2
from networking_odl.ml2 import mech_driver_v2
from networking_odl.tests import base
from networking_odl.tests.unit.journal import helper
from networking_odl.tests.unit import test_base_db


@@ -208,3 +210,26 @@ class RecoveryTestCase(test_base_db.ODLBaseDbTestCase):
rmock.side_effect = nexc.NotFound
self._disable_retention()
self._test_recovery(odl_const.ODL_UPDATE, None, None)

def _test_get_latest_resource(self, resource_type):
# Drivers needs to be initialized to register resources for recovery
# and full sync mechasnim.
helper.TestDriver()
directory.add_plugin(helper.TEST_PLUGIN, helper.TestPlugin())
self.addCleanup(directory.add_plugin, helper.TEST_PLUGIN, None)
return db.create_pending_row(self.db_context.session, resource_type,
'id', odl_const.ODL_DELETE, {})

def test_get_latest_resource(self):
row = self._test_get_latest_resource(helper.TEST_RESOURCE1)
plugin = directory.get_plugin(helper.TEST_PLUGIN)
resource = recovery.get_latest_resource(self.db_context, row)
self.assertDictEqual(resource,
plugin.get_test_resource1(self.db_context, 'id'))

def test_get_unsupported_latest_resource(self):
row = self._test_get_latest_resource(helper.TEST_RESOURCE1)
row.object_type = helper.INVALID_RESOURCE
self.assertRaises(exceptions.UnsupportedResourceType,
recovery.get_latest_resource,
self.db_context, row)

Loading…
Cancel
Save