Split Provider configuration into files

Change-Id: Ie88895ceda3a620aa33999a980e04e144d8f32e6
This commit is contained in:
Yuval Brik 2016-04-04 23:36:30 +03:00
parent e90c2356d1
commit 7b24765f4a
24 changed files with 314 additions and 202 deletions

View File

@ -0,0 +1,9 @@
[provider]
name = OS Infra Provider
description = This provider uses OpenStack's own services (swift, cinder) as storage
id = cf56bd3e-97a7-4078-b6d5-f36246333fd9
# TODO(yuvalbr)
# bank = swift
# plugin = cinder_backup
# plugin = glance_backup
# plugin = neutron_backup

View File

@ -80,11 +80,6 @@ global_opts = [
cfg.IntOpt('lease_validity_window',
default=100,
help='validity_window for bank lease, in seconds'),
cfg.ListOpt('enabled_providers',
default=None,
help='A list of provider names to use. These provider names '
'should be backed by a unique [CONFIG] group '
'with its options'),
]
CONF.register_opts(global_opts)

View File

@ -45,6 +45,9 @@ class LeasePlugin(object):
@six.add_metaclass(abc.ABCMeta)
class BankPlugin(object):
def __init__(self, config=None):
self._config = config
@abc.abstractmethod
def create_object(self, key, value):
return

View File

@ -60,8 +60,6 @@ swift_client_opts = [
'making SSL connection to Swift.'),
]
CONF = cfg.CONF
CONF.register_opts(swift_client_opts, "swift_client")
LOG = logging.getLogger(__name__)
@ -70,19 +68,24 @@ class SwiftConnectionFailed(exception.SmaugException):
class SwiftBankPlugin(BankPlugin, LeasePlugin):
def __init__(self, context, object_container):
super(BankPlugin, self).__init__()
def __init__(self, config, context, object_container):
super(SwiftBankPlugin, self).__init__(config)
self.context = context
self.swift_retry_attempts = CONF.swift_client.bank_swift_retry_attempts
self.swift_retry_backoff = CONF.swift_client.bank_swift_retry_backoff
self.swift_auth_insecure = CONF.swift_client.bank_swift_auth_insecure
self.swift_ca_cert_file = CONF.swift_client.bank_swift_ca_cert_file
self.lease_expire_window = CONF.lease_expire_window
self.lease_renew_window = CONF.lease_renew_window
self._config.register_opts(swift_client_opts, "swift_client")
self.swift_retry_attempts = \
self._config.swift_client.bank_swift_retry_attempts
self.swift_retry_backoff = \
self._config.swift_client.bank_swift_retry_backoff
self.swift_auth_insecure = \
self._config.swift_client.bank_swift_auth_insecure
self.swift_ca_cert_file = \
self._config.swift_client.bank_swift_ca_cert_file
self.lease_expire_window = self._config.lease_expire_window
self.lease_renew_window = self._config.lease_renew_window
# TODO(luobin):
# init lease_validity_window
# according to lease_renew_window if not configured
self.lease_validity_window = CONF.lease_validity_window
self.lease_validity_window = self._config.lease_validity_window
# TODO(luobin): create a uuid of this bank_plugin
self.owner_id = str(uuid.uuid4())
@ -113,20 +116,20 @@ class SwiftBankPlugin(BankPlugin, LeasePlugin):
initial_delay=self.lease_renew_window)
def _setup_connection(self):
if CONF.swift_client.bank_swift_auth == "single_user":
if self._config.swift_client.bank_swift_auth == "single_user":
connection = swift.Connection(
authurl=CONF.swift_client.bank_swift_auth_url,
auth_version=CONF.swift_client.bank_swift_auth_version,
tenant_name=CONF.swift_client.bank_swift_tenant_name,
user=CONF.swift_client.bank_swift_user,
key=CONF.swift_client.bank_swift_key,
authurl=self._config.swift_client.bank_swift_auth_url,
auth_version=self._config.swift_client.bank_swift_auth_version,
tenant_name=self._config.swift_client.bank_swift_tenant_name,
user=self._config.swift_client.bank_swift_user,
key=self._config.swift_client.bank_swift_key,
retries=self.swift_retry_attempts,
starting_backoff=self.swift_retry_backoff,
insecure=self.swift_auth_insecure,
cacert=self.swift_ca_cert_file)
else:
connection = swift.Connection(
preauthurl=CONF.swift_client.bank_swift_url,
preauthurl=self._config.swift_client.bank_swift_url,
preauthtoken=self.context.auth_token,
retries=self.swift_retry_attempts,
starting_backoff=self.swift_retry_backoff,

View File

@ -12,6 +12,7 @@
import os
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from smaug.i18n import _LE
@ -36,11 +37,11 @@ class ClientFactory(object):
yield '%s.clients.%s' % (__package__, name)
@classmethod
def create_client(cls, service, context):
def create_client(cls, service, context, conf=cfg.CONF):
if not cls._factory:
cls._factory = {}
for module in cls._list_clients():
module = importutils.import_module(module)
cls._factory[module.SERVICE] = module
return cls._factory[service].create(context)
return cls._factory[service].create(context, conf)

View File

@ -35,9 +35,10 @@ cfg.CONF.register_opts(cinder_client_opts, group=SERVICE + '_client')
CINDERCLIENT_VERSION = '2'
def create(context):
def create(context, conf):
conf.register_opts(cinder_client_opts, group=SERVICE + '_client')
try:
url = utils.get_url(SERVICE, context, append_project=True)
url = utils.get_url(SERVICE, context, conf, append_project=True)
except Exception:
LOG.error(_LE("Get cinder service endpoint url failed."))
raise

View File

@ -35,9 +35,10 @@ cfg.CONF.register_opts(glance_client_opts, group=SERVICE + '_client')
GLANCECLIENT_VERSION = '2'
def create(context):
def create(context, conf):
conf.register_opts(glance_client_opts, group=SERVICE + '_client')
try:
url = utils.get_url(SERVICE, context)
url = utils.get_url(SERVICE, context, conf)
except Exception:
LOG.error(_LE("Get glance service endpoint url failed"))
raise

View File

@ -33,9 +33,10 @@ neutron_client_opts = [
cfg.CONF.register_opts(neutron_client_opts, group=SERVICE + '_client')
def create(context):
def create(context, conf):
conf.register_opts(neutron_client_opts, group=SERVICE + '_client')
try:
url = utils.get_url(SERVICE, context)
url = utils.get_url(SERVICE, context, conf)
except Exception:
LOG.error(_LE("Get neutron service endpoint url failed"))
raise

View File

@ -36,9 +36,10 @@ cfg.CONF.register_opts(nova_client_opts, group=SERVICE + '_client')
NOVACLIENT_VERSION = '2'
def create(context):
def create(context, conf):
conf.register_opts(nova_client_opts, group=SERVICE + '_client')
try:
url = utils.get_url(SERVICE, context, append_project=True)
url = utils.get_url(SERVICE, context, conf, append_project=True)
except Exception:
LOG.error(_LE("Get nova service endpoint url failed."))
raise

View File

@ -24,6 +24,8 @@ LOG = logging.getLogger(__name__)
@six.add_metaclass(abc.ABCMeta)
class ProtectionPlugin(object):
def __init__(self, config=None):
self._config = config
@abc.abstractmethod
def get_supported_resources_types(self):

View File

@ -10,22 +10,28 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_config import cfg
from oslo_log import log as logging
from smaug.common import constants
from smaug.i18n import _LE
from smaug.services.protection import checkpoint
from smaug import utils
provider_opt = [
provider_opts = [
cfg.MultiStrOpt('plugin',
default='',
help='plugins to use for protection'),
cfg.StrOpt('bank',
default='',
help='bank plugin to use for storage'),
cfg.StrOpt('description',
default='',
help='the description of provider'),
cfg.StrOpt('provider_id',
cfg.StrOpt('name',
default='',
help='the name of provider'),
cfg.StrOpt('id',
default='',
help='the provider id')
]
@ -35,13 +41,20 @@ LOG = logging.getLogger(__name__)
PROTECTION_NAMESPACE = 'smaug.protections'
CONF.register_opt(cfg.StrOpt('provider_config_dir',
default='providers.d',
help='Configuration directory for providers.'
' Absolute path, or relative to smaug '
' configuration directory.'))
class PluggableProtectionProvider(object):
def __init__(self, provider_id, provider_name, description, plugins):
def __init__(self, provider_config):
super(PluggableProtectionProvider, self).__init__()
self._id = provider_id
self._name = provider_name
self._description = description
self._config = provider_config
self._id = self._config.provider.id
self._name = self._config.provider.name
self._description = self._config.provider.description
self._extended_info_schema = {'options_schema': {},
'restore_schema': {},
'saved_info_schema': {}}
@ -49,12 +62,21 @@ class PluggableProtectionProvider(object):
self._bank_plugin = None
self._plugin_map = {}
self._load_plugins(plugins=plugins)
if hasattr(self._config.provider, 'bank') \
and not self._config.provider.bank:
raise ImportError("Empty bank")
self._load_bank(self._config.provider.bank)
if hasattr(self._config.provider, 'plugin'):
for plugin_name in self._config.provider.plugin:
if not plugin_name:
raise ImportError("Empty protection plugin")
self._load_plugin(plugin_name)
if self._bank_plugin:
self.checkpoint_collection = checkpoint.CheckpointCollection(
self._bank_plugin)
else:
LOG.error(_LE('Bank plugin not exist,check your configuration'))
LOG.error(_LE('Bank plugin not exist, check your configuration'))
@property
def id(self):
@ -72,27 +94,43 @@ class PluggableProtectionProvider(object):
def extended_info_schema(self):
return self._extended_info_schema
def _load_plugins(self, plugins):
for plugin_name in plugins:
try:
plugin = utils.load_plugin(PROTECTION_NAMESPACE, plugin_name)
except Exception:
LOG.exception(_LE("Load protection plugin: %s failed."),
plugin_name)
raise
else:
self._plugin_map[plugin_name] = plugin
if constants.PLUGIN_BANK in plugin_name.lower():
self._bank_plugin = plugin
@property
def bank(self):
return self._bank_plugin
@property
def plugins(self):
return self._plugin_map
def _load_bank(self, bank_name):
try:
plugin = utils.load_plugin(PROTECTION_NAMESPACE, bank_name,
self._config)
except Exception:
LOG.error(_LE("Load bank plugin: '%s' failed."), bank_name)
raise
else:
self._bank_plugin = plugin
def _load_plugin(self, plugin_name):
try:
plugin = utils.load_plugin(PROTECTION_NAMESPACE, plugin_name,
self._config)
except Exception:
LOG.error(_LE("Load protection plugin: '%s' failed."), plugin_name)
raise
else:
self._plugin_map[plugin_name] = plugin
for resource in plugin.get_supported_resources_types():
if hasattr(plugin, 'get_options_schema'):
self._extended_info_schema['options_schema'][plugin_name] \
= plugin.get_options_schema()
self._extended_info_schema['options_schema'][resource] \
= plugin.get_options_schema(resource)
if hasattr(plugin, 'get_restore_schema'):
self._extended_info_schema['restore_schema'][plugin_name] \
= plugin.get_restore_schema()
self._extended_info_schema['restore_schema'][resource] \
= plugin.get_restore_schema(resource)
if hasattr(plugin, 'get_saved_info_schema'):
self._extended_info_schema['saved_info_schema'][plugin_name] \
= plugin.get_saved_info_schema()
self._extended_info_schema['saved_info_schema'][resource] \
= plugin.get_saved_info_schema(resource)
def get_checkpoint_collection(self):
return self.checkpoint_collection
@ -109,51 +147,29 @@ class ProviderRegistry(object):
self._load_providers()
def _load_providers(self):
"""load provider
"""load provider"""
config_dir = utils.find_config(CONF.provider_config_dir)
smaug.conf example:
[default]
enabled_providers=provider1,provider2
[provider1]
provider_id='' configured by admin
plugin=BANK define in setup.cfg
plugin=VolumeProtectionPlugin define in setup.cfg
description='the description of provider1'
[provider2]
provider_id='' configured by admin
plugin=BANK define in setup.cfg
plugin=VolumeProtectionPlugin define in setup.cfg
plugin=ServerProtectionPlugin define in setup.cfg
description='the description of provider2'
"""
if CONF.enabled_providers:
for provider_name in CONF.enabled_providers:
CONF.register_opts(provider_opt, group=provider_name)
plugins = getattr(CONF, provider_name).plugin
description = getattr(CONF, provider_name).description
provider_id = getattr(CONF, provider_name).provider_id
if not all([plugins, provider_id]):
LOG.error(_LE("Invalid provider:%s,check provider"
" configuration"),
provider_name)
continue
try:
provider = PluggableProtectionProvider(provider_id,
provider_name,
description,
plugins)
except Exception:
LOG.exception(_LE("Load provider: %s failed."),
provider_name)
else:
self.providers[provider_id] = provider
for config_file in os.listdir(config_dir):
if not config_file.endswith('.conf'):
continue
config_path = os.path.abspath(os.path.join(config_dir,
config_file))
provider_config = cfg.ConfigOpts()
provider_config(args=['--config-file=' + config_path])
provider_config.register_opts(provider_opts, 'provider')
try:
provider = PluggableProtectionProvider(provider_config)
except Exception:
LOG.error(_LE("Load provider: %s failed."),
provider_config.provider.name)
else:
self.providers[provider.id] = provider
def list_providers(self, list_option=None):
if not list_option:
return [dict(id=provider.id, name=provider.name,
description=provider.description)
for provider in self.providers.values()]
# It seems that we don't need list_option
def list_providers(self):
return [dict(id=provider.id, name=provider.name,
description=provider.description)
for provider in self.providers.values()]
def show_provider(self, provider_id):
return self.providers.get(provider_id, None)

View File

@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from smaug import exception
from smaug.i18n import _
@ -39,9 +38,9 @@ def _parse_service_endpoint(endpoint_url, context, append_project=False):
else endpoint_url
def get_url(service, context, append_project=False):
def get_url(service, context, conf, append_project=False):
'''Return the url of given service endpoint.'''
client_conf = getattr(cfg.CONF, service + '_client')
client_conf = getattr(conf, service + '_client')
endpoint = getattr(client_conf, service + '_endpoint')
if endpoint is not None:

View File

@ -35,13 +35,13 @@ class CinderClientTest(base.TestCase):
cfg.CONF.set_default('cinder_endpoint',
'http://127.0.0.1:8776/v2',
'cinder_client')
client = cinder.create(self._context)
client = cinder.create(self._context, cfg.CONF)
self.assertEqual('volumev2', client.client.service_type)
self.assertEqual('http://127.0.0.1:8776/v2/abcd',
client.client.management_url)
def test_create_client_by_catalog(self):
client = cinder.create(self._context)
client = cinder.create(self._context, cfg.CONF)
self.assertEqual('volumev2', client.client.service_type)
self.assertEqual('http://127.0.0.1:8776/v2/abcd',
client.client.management_url)

View File

@ -40,9 +40,9 @@ class GlanceClientTest(base.TestCase):
cfg.CONF.set_default('glance_endpoint',
'http://127.0.0.1:9292',
'glance_client')
gc = glance.create(self._context)
gc = glance.create(self._context, cfg.CONF)
self.assertEqual('http://127.0.0.1:9292', gc.http_client.endpoint)
def test_create_client_by_catalog(self):
gc = glance.create(self._context)
gc = glance.create(self._context, cfg.CONF)
self.assertEqual('http://127.0.0.1:9292', gc.http_client.endpoint)

View File

@ -40,9 +40,9 @@ class NeutronClientTest(base.TestCase):
cfg.CONF.set_default('neutron_endpoint',
'http://127.0.0.1:9696',
'neutron_client')
nc = neutron.create(self._context)
nc = neutron.create(self._context, cfg.CONF)
self.assertEqual('http://127.0.0.1:9696', nc.httpclient.endpoint_url)
def test_create_client_by_catalog(self):
nc = neutron.create(self._context)
nc = neutron.create(self._context, cfg.CONF)
self.assertEqual('http://127.0.0.1:9696', nc.httpclient.endpoint_url)

View File

@ -35,13 +35,13 @@ class NovaClientTest(base.TestCase):
cfg.CONF.set_default('nova_endpoint',
'http://127.0.0.1:8774/v2.1',
'nova_client')
client = nova.create(self._context)
client = nova.create(self._context, cfg.CONF)
self.assertEqual('compute', client.client.service_type)
self.assertEqual('http://127.0.0.1:8774/v2.1/abcd',
client.client.management_url)
def test_create_client_by_catalog(self):
client = nova.create(self._context)
client = nova.create(self._context, cfg.CONF)
self.assertEqual('compute', client.client.service_type)
self.assertEqual('http://127.0.0.1:8774/v2.1/abcd',
client.client.management_url)

View File

@ -28,3 +28,5 @@ def set_defaults(conf):
conf.set_default('auth_strategy', 'noauth')
conf.set_default('state_path', os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..')))
conf.set_default('provider_config_dir',
os.path.join(os.path.dirname(__file__), 'fake_providers'))

View File

@ -0,0 +1,40 @@
# 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 oslo_config import cfg
from smaug.services.protection import bank_plugin
fake_bank_opts = [
cfg.StrOpt('fake_host'),
]
class FakeBankPlugin(bank_plugin.BankPlugin):
def __init__(self, config=None):
super(FakeBankPlugin, self).__init__(config)
config.register_opts(fake_bank_opts, 'fake_bank')
def create_object(self, key, value):
return
def update_object(self, key, value):
return
def get_object(self, key):
return
def list_objects(self, prefix=None, limit=None, marker=None):
return
def delete_object(self, key):
return

View File

@ -0,0 +1,49 @@
# 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 oslo_config import cfg
from smaug.services.protection import protection_plugin
fake_plugin_opts = [
cfg.StrOpt('fake_user'),
]
class FakeProtectionPlugin(protection_plugin.ProtectionPlugin):
def __init__(self, config=None):
super(FakeProtectionPlugin, self).__init__(config)
config.register_opts(fake_plugin_opts, 'fake_plugin')
def get_supported_resources_types(self):
return ['Test::Resource']
def get_options_schema(self, resource_type):
return []
def get_saved_info_schema(self, resource_type):
return []
def get_restore_schema(self, resource_type):
return []
def get_saved_info(self, metadata_store, resource):
pass
def get_protection_stats(self, protection_id):
pass
def on_resource_start(self, context):
pass
def on_resource_end(self, context):
pass

View File

@ -0,0 +1,12 @@
[provider]
name = fake_provider1
id = fake_id1
description = Test Provider 1
bank = smaug.tests.unit.fake_bank.FakeBankPlugin
plugin = smaug.tests.unit.fake_protection.FakeProtectionPlugin
[fake_plugin]
fake_user = user
[fake_bank]
fake_host = thor

View File

@ -0,0 +1,4 @@
[provider]
name = fake_provider2
id = fake_id2
description = Test Provider 2

View File

@ -11,75 +11,48 @@
# under the License.
import mock
from oslo_config import cfg
from smaug.services.protection import provider
from smaug.tests import base
provider_opt = [
cfg.MultiStrOpt('plugin',
default='',
help='plugins to use for protection'),
cfg.StrOpt('description',
default='',
help='the description of provider'),
cfg.StrOpt('provider_id',
default='',
help='the provider id')
]
CONF = cfg.CONF
class ProviderRegistryTest(base.TestCase):
def setUp(self):
super(ProviderRegistryTest, self).setUp()
CONF.set_override('enabled_providers',
['provider1', 'provider2'])
CONF.register_opts(provider_opt, group='provider1')
CONF.register_opts(provider_opt, group='provider2')
CONF.set_override('plugin', ['SERVER', 'VOLUME'],
group='provider1')
CONF.set_override('plugin', ['SERVER'],
group='provider2')
CONF.set_override('description', 'FAKE1', group='provider1')
CONF.set_override('description', 'FAKE2', group='provider2')
CONF.set_override('provider_id', 'id1', group='provider1')
CONF.set_override('provider_id', 'id2', group='provider2')
@mock.patch.object(provider.PluggableProtectionProvider, '_load_plugins')
def test_load_providers(self, mock_load_plugins):
CONF.set_override('plugin', ['SERVER'],
group='provider2')
@mock.patch.object(provider.PluggableProtectionProvider, '_load_bank')
@mock.patch.object(provider.PluggableProtectionProvider, '_load_plugin')
def test_load_providers(self, mock_load_bank, mock_load_plugin):
pr = provider.ProviderRegistry()
self.assertTrue(mock_load_plugins.called)
self.assertEqual(len(pr.providers), 2)
@mock.patch.object(provider.PluggableProtectionProvider, '_load_plugins')
def test_load_providers_with_no_plugins(self, mock_load_plugins):
CONF.set_override('plugin', None,
group='provider2')
pr = provider.ProviderRegistry()
self.assertEqual(mock_load_plugins.call_count, 1)
self.assertEqual(mock_load_plugin.call_count, 1)
self.assertEqual(mock_load_bank.call_count, 1)
self.assertEqual(len(pr.providers), 1)
@mock.patch.object(provider.PluggableProtectionProvider, '_load_plugins')
def test_list_provider(self, mock_load_plugins):
CONF.set_override('plugin', ['SERVER'],
group='provider2')
pr = provider.ProviderRegistry()
self.assertEqual(2, len(pr.list_providers()))
self.assertEqual(pr.providers['fake_id1'].name, 'fake_provider1')
self.assertNotIn('fake_provider2', pr.providers)
@mock.patch.object(provider.PluggableProtectionProvider, '_load_plugins')
def test_show_provider(self, mock_load_plugins):
CONF.set_override('plugin', ['SERVER'],
group='provider2')
def test_provider_bank_config(self):
pr = provider.ProviderRegistry()
provider1 = pr.show_provider('fake_id1')
self.assertEqual(provider1.bank._config.fake_bank.fake_host, 'thor')
def test_provider_plugin_config(self):
pr = provider.ProviderRegistry()
provider1 = pr.show_provider('fake_id1')
plugin_name = 'smaug.tests.unit.fake_protection.FakeProtectionPlugin'
self.assertEqual(
provider1.plugins[plugin_name]._config.fake_plugin.fake_user,
'user')
def test_list_provider(self):
pr = provider.ProviderRegistry()
self.assertEqual(1, len(pr.list_providers()))
def test_show_provider(self):
pr = provider.ProviderRegistry()
provider_list = pr.list_providers()
for provider_node in provider_list:
self.assertTrue(pr.show_provider(provider_node['id']))
def tearDown(self):
CONF.register_opts(provider_opt, group='provider1')
CONF.register_opts(provider_opt, group='provider2')
CONF.set_override('enabled_providers',
None)
super(ProviderRegistryTest, self).tearDown()

View File

@ -41,7 +41,7 @@ class SwiftBankPluginTest(base.TestCase):
import_str=import_str)
swift.Connection = mock.MagicMock()
swift.Connection.return_value = self.fake_connection
self.swift_bank_plugin = swift_bank_plugin_cls(None,
self.swift_bank_plugin = swift_bank_plugin_cls(CONF, None,
self.object_container)
def test_acquire_lease(self):

View File

@ -125,7 +125,7 @@ def get_bool_param(param_string, params):
return strutils.bool_from_string(param, strict=True)
def load_plugin(namespace, plugin_name):
def load_plugin(namespace, plugin_name, *args, **kwargs):
try:
# Try to resolve plugin by name
mgr = driver.DriverManager(namespace, plugin_name)
@ -138,4 +138,4 @@ def load_plugin(namespace, plugin_name):
LOG.exception(_LE("Error loading plugin by name, %s"), e1)
LOG.exception(_LE("Error loading plugin by class, %s"), e2)
raise ImportError(_("Class not found."))
return plugin_class()
return plugin_class(*args, **kwargs)