Merge "Nailgun extensions in stevedore"
This commit is contained in:
commit
d8ae4b399a
@ -15,12 +15,12 @@
|
||||
# under the License.
|
||||
|
||||
from nailgun.extensions.base import BaseExtension
|
||||
from nailgun.extensions.base import get_extension
|
||||
from nailgun.extensions.base import get_all_extensions
|
||||
from nailgun.extensions.base import node_extension_call
|
||||
from nailgun.extensions.base import fire_callback_on_node_delete
|
||||
from nailgun.extensions.base import fire_callback_on_node_collection_delete
|
||||
from nailgun.extensions.base import fire_callback_on_node_create
|
||||
from nailgun.extensions.base import fire_callback_on_node_update
|
||||
from nailgun.extensions.base import fire_callback_on_node_reset
|
||||
from nailgun.extensions.base import fire_callback_on_cluster_delete
|
||||
from nailgun.extensions.manager import get_extension
|
||||
from nailgun.extensions.manager import get_all_extensions
|
||||
from nailgun.extensions.manager import node_extension_call
|
||||
from nailgun.extensions.manager import fire_callback_on_node_delete
|
||||
from nailgun.extensions.manager import fire_callback_on_node_collection_delete
|
||||
from nailgun.extensions.manager import fire_callback_on_node_create
|
||||
from nailgun.extensions.manager import fire_callback_on_node_update
|
||||
from nailgun.extensions.manager import fire_callback_on_node_reset
|
||||
from nailgun.extensions.manager import fire_callback_on_cluster_delete
|
||||
|
@ -14,118 +14,37 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"Contains base class for Nailgun extensions"
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from nailgun.errors import errors
|
||||
|
||||
|
||||
def get_all_extensions():
|
||||
# TODO(eli): implement extensions autodiscovery
|
||||
# should be done as a part of blueprint
|
||||
# https://blueprints.launchpad.net/fuel/+spec/volume-manager-refactoring
|
||||
from nailgun.extensions.cluster_upgrade.extension \
|
||||
import ClusterUpgradeExtension
|
||||
from nailgun.extensions.volume_manager.extension \
|
||||
import VolumeManagerExtension
|
||||
|
||||
extensions = [
|
||||
VolumeManagerExtension,
|
||||
ClusterUpgradeExtension,
|
||||
]
|
||||
return extensions
|
||||
|
||||
|
||||
def get_extension(name):
|
||||
"""Retrieves extension by name
|
||||
|
||||
:param str name: name of the extension
|
||||
:returns: extension class
|
||||
"""
|
||||
extensions = filter(lambda e: e.name == name, get_all_extensions())
|
||||
|
||||
if not extensions:
|
||||
raise errors.CannotFindExtension(
|
||||
"Cannot find extension with name '{0}'".format(name))
|
||||
|
||||
return extensions[0]
|
||||
|
||||
|
||||
def _get_extension_by_node_or_env(call_name, node):
|
||||
found_extension = None
|
||||
|
||||
# Try to find extension in node
|
||||
if node:
|
||||
for extension in node.extensions:
|
||||
if call_name in get_extension(extension).provides:
|
||||
found_extension = extension
|
||||
|
||||
# Try to find extension by environment
|
||||
if not found_extension and node.cluster:
|
||||
for extension in node.cluster.extensions:
|
||||
if call_name in get_extension(extension).provides:
|
||||
found_extension = extension
|
||||
|
||||
if not found_extension:
|
||||
raise errors.CannotFindExtension(
|
||||
"Cannot find extension which provides "
|
||||
"'{0}' call".format(call_name))
|
||||
|
||||
return get_extension(found_extension)
|
||||
|
||||
|
||||
def node_extension_call(call_name, node, *args, **kwargs):
|
||||
extension = _get_extension_by_node_or_env(call_name, node)
|
||||
|
||||
return getattr(extension, call_name)(node, *args, **kwargs)
|
||||
|
||||
|
||||
def fire_callback_on_node_create(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_create(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_update(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_update(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_reset(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_reset(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_delete(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_delete(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_collection_delete(node_ids):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_collection_delete(node_ids)
|
||||
|
||||
|
||||
def fire_callback_on_cluster_delete(cluster):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_cluster_delete(cluster)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseExtension(object):
|
||||
"""Base class for Nailgun extension
|
||||
|
||||
# If extension provides API, define here urls in then
|
||||
# next format:
|
||||
# [
|
||||
# {
|
||||
# "uri": r'/new/url',
|
||||
# "handler": HandlerClass
|
||||
# }
|
||||
# ]
|
||||
If extension provides API, define here urls in then following format:
|
||||
urls = [
|
||||
{
|
||||
"uri": r'/new/url',
|
||||
"handler": HandlerClass
|
||||
}
|
||||
]
|
||||
urls = []
|
||||
|
||||
# Specify a list of calls which extension provides.
|
||||
# This list is required for core and other extensions
|
||||
# to find extension with specific functionality.
|
||||
Specify a list of calls which extension provides.
|
||||
This list is required for core and other extensions
|
||||
to find extension with specific functionality.
|
||||
|
||||
provides = [
|
||||
'method_1',
|
||||
'method_2',
|
||||
]
|
||||
"""
|
||||
|
||||
urls = []
|
||||
provides = []
|
||||
|
||||
@classmethod
|
||||
@ -137,6 +56,10 @@ class BaseExtension(object):
|
||||
def name(self):
|
||||
"""Uniq name of the extension."""
|
||||
|
||||
@abc.abstractproperty
|
||||
def description(self):
|
||||
"""Brief description of extension"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def version(self):
|
||||
"""Version of the extension
|
||||
|
@ -24,6 +24,7 @@ from . import handlers
|
||||
class ClusterUpgradeExtension(extensions.BaseExtension):
|
||||
name = 'cluster_upgrade'
|
||||
version = '0.0.1'
|
||||
description = "Cluster Upgrade Extension"
|
||||
|
||||
urls = [
|
||||
{'uri': r'/clusters/(?P<cluster_id>\d+)/upgrade/clone/?$',
|
||||
|
@ -18,3 +18,5 @@
|
||||
# not hardcode information about tables which are specific
|
||||
# for extension or core into extension's and core's migrations.
|
||||
extensions_migration_buffer_table_name = 'extensions_migration_buffer'
|
||||
|
||||
EXTENSIONS_NAMESPACE = 'nailgun.extensions'
|
||||
|
108
nailgun/nailgun/extensions/manager.py
Normal file
108
nailgun/nailgun/extensions/manager.py
Normal file
@ -0,0 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2016 Mirantis, Inc.
|
||||
#
|
||||
# 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 itertools import chain
|
||||
|
||||
from stevedore.extension import ExtensionManager
|
||||
|
||||
from nailgun.errors import errors
|
||||
from nailgun.extensions import consts
|
||||
|
||||
_EXTENSION_MANAGER = None
|
||||
|
||||
|
||||
def get_all_extensions():
|
||||
"""Retrieves all available extensions for Nailgun
|
||||
|
||||
:returns: generator of extensions objects
|
||||
"""
|
||||
# NOTE(sbrzeczkowski): in order to not cause circular imports from
|
||||
# extensions and not creating instances ExtensionManager on every time
|
||||
# this function is called, we use 'global' variable.
|
||||
global _EXTENSION_MANAGER
|
||||
|
||||
if _EXTENSION_MANAGER is None:
|
||||
_EXTENSION_MANAGER = ExtensionManager(
|
||||
namespace=consts.EXTENSIONS_NAMESPACE)
|
||||
|
||||
return (ext.plugin for ext in _EXTENSION_MANAGER.extensions)
|
||||
|
||||
|
||||
def get_extension(name):
|
||||
"""Retrieves extension by name
|
||||
|
||||
:param str name: name of the extension
|
||||
:returns: extension class
|
||||
:raises errors.CannotFindExtension: on non existing extension
|
||||
"""
|
||||
extension = next(
|
||||
(ext for ext in get_all_extensions() if ext.name == name), None)
|
||||
|
||||
if extension is not None:
|
||||
return extension
|
||||
|
||||
raise errors.CannotFindExtension(
|
||||
"Cannot find extension with name '{0}'".format(name))
|
||||
|
||||
|
||||
def _get_extension_by_node(call_name, node):
|
||||
all_extensions = {ext.name: ext for ext in get_all_extensions()}
|
||||
for extension in chain(node.extensions,
|
||||
node.cluster.extensions if node.cluster else []):
|
||||
|
||||
if (extension in all_extensions and
|
||||
call_name in all_extensions[extension].provides):
|
||||
return all_extensions[extension]
|
||||
|
||||
raise errors.CannotFindExtension("Cannot find extension which provides "
|
||||
"'{0}' call".format(call_name))
|
||||
|
||||
|
||||
def node_extension_call(call_name, node, *args, **kwargs):
|
||||
# NOTE(sbrzeczkowski): should be removed once data-pipeline blueprint is
|
||||
# done: https://blueprints.launchpad.net/fuel/+spec/data-pipeline
|
||||
extension = _get_extension_by_node(call_name, node)
|
||||
return getattr(extension, call_name)(node, *args, **kwargs)
|
||||
|
||||
|
||||
def fire_callback_on_node_create(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_create(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_update(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_update(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_reset(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_reset(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_delete(node):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_delete(node)
|
||||
|
||||
|
||||
def fire_callback_on_node_collection_delete(node_ids):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_node_collection_delete(node_ids)
|
||||
|
||||
|
||||
def fire_callback_on_cluster_delete(cluster):
|
||||
for extension in get_all_extensions():
|
||||
extension.on_cluster_delete(cluster)
|
@ -37,6 +37,8 @@ class VolumeManagerExtension(BaseExtension):
|
||||
'set_node_volumes',
|
||||
'set_default_node_volumes']
|
||||
|
||||
description = "Volume Manager Extension"
|
||||
|
||||
@classmethod
|
||||
def alembic_migrations_path(cls):
|
||||
return os.path.join(os.path.dirname(__file__),
|
||||
|
@ -34,11 +34,12 @@ class TestBaseExtension(BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestBaseExtension, self).setUp()
|
||||
|
||||
class Extexnsion(BaseExtension):
|
||||
class Extension(BaseExtension):
|
||||
name = 'ext_name'
|
||||
version = '1.0.0'
|
||||
description = 'ext description'
|
||||
|
||||
self.extension = Extexnsion()
|
||||
self.extension = Extension()
|
||||
|
||||
def test_alembic_table_version(self):
|
||||
self.assertEqual(
|
||||
@ -82,12 +83,13 @@ class TestExtensionUtils(BaseTestCase):
|
||||
|
||||
return node
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_get_extension(self, get_m):
|
||||
get_extension('ex1')
|
||||
extension = get_extension('ex1')
|
||||
self.assertEqual(extension.name, 'ex1')
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_get_extension_raises_errors(self, get_m):
|
||||
self.assertRaisesRegexp(
|
||||
@ -96,7 +98,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
get_extension,
|
||||
'unknown_ex')
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_node_extension_call_raises_error(self, _):
|
||||
self.assertRaisesRegexp(
|
||||
@ -106,7 +108,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
'method_call',
|
||||
self.make_node())
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_node_extension_call_extension_from_node(self, get_m):
|
||||
node = self.make_node(
|
||||
@ -122,7 +124,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
ex1.method_call.assert_called_once_with(node)
|
||||
self.assertFalse(ex2.method_call.called)
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_node_extension_call_default_extension_from_cluster(self, get_m):
|
||||
node = self.make_node(
|
||||
@ -138,7 +140,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
self.assertFalse(ex1.method_call.called)
|
||||
ex2.method_call.assert_called_once_with(node)
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_fire_callback_on_node_create(self, get_m):
|
||||
node = mock.MagicMock()
|
||||
@ -147,7 +149,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
for ext in get_m.return_value:
|
||||
ext.on_node_create.assert_called_once_with(node)
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_fire_callback_on_node_update(self, get_m):
|
||||
node = mock.MagicMock()
|
||||
@ -156,7 +158,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
for ext in get_m.return_value:
|
||||
ext.on_node_update.assert_called_once_with(node)
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_fire_callback_on_node_reset(self, get_m):
|
||||
node = mock.MagicMock()
|
||||
@ -165,7 +167,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
for ext in get_m.return_value:
|
||||
ext.on_node_reset.assert_called_once_with(node)
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_fire_callback_on_node_delete(self, get_m):
|
||||
node = mock.MagicMock()
|
||||
@ -174,7 +176,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
for ext in get_m.return_value:
|
||||
ext.on_node_delete.assert_called_once_with(node)
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_fire_callback_on_node_collection_delete(self, get_m):
|
||||
node_ids = [1, 2, 3, 4]
|
||||
@ -183,7 +185,7 @@ class TestExtensionUtils(BaseTestCase):
|
||||
for ext in get_m.return_value:
|
||||
ext.on_node_collection_delete.assert_called_once_with(node_ids)
|
||||
|
||||
@mock.patch('nailgun.extensions.base.get_all_extensions',
|
||||
@mock.patch('nailgun.extensions.manager.get_all_extensions',
|
||||
return_value=make_mock_extensions())
|
||||
def test_fire_callback_on_cluster_deletion(self, get_m):
|
||||
cluster = mock.MagicMock()
|
||||
|
@ -45,38 +45,45 @@ def recursive_data_files(spec_data_files):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup(name=name,
|
||||
version=version,
|
||||
description='Nailgun package',
|
||||
long_description="""Nailgun package""",
|
||||
classifiers=[
|
||||
setup(
|
||||
name=name,
|
||||
version=version,
|
||||
description='Nailgun package',
|
||||
long_description="""Nailgun package""",
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
|
||||
],
|
||||
author='Mirantis Inc.',
|
||||
author_email='product@mirantis.com',
|
||||
url='http://mirantis.com',
|
||||
keywords='web wsgi nailgun mirantis',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
install_requires=find_requires(),
|
||||
include_package_data=True,
|
||||
scripts=['manage.py'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'nailgun_syncdb = nailgun.db:syncdb',
|
||||
'nailgun_fixtures = \
|
||||
nailgun.db.sqlalchemy.fixman:upload_fixtures',
|
||||
'nailgund = nailgun.app:appstart',
|
||||
'assassind = nailgun.assassin.assassind:run',
|
||||
'receiverd = nailgun.rpc.receiverd:run',
|
||||
'statsenderd = nailgun.statistics.statsenderd:run',
|
||||
'oswl_collectord = nailgun.statistics.oswl.collector:run',
|
||||
('oswl_cleaner = nailgun.statistics.oswl.helpers:'
|
||||
'delete_expired_oswl_entries'),
|
||||
],
|
||||
},
|
||||
data_files=recursive_data_files([('share/nailgun', 'static')])
|
||||
)
|
||||
author='Mirantis Inc.',
|
||||
author_email='product@mirantis.com',
|
||||
url='http://mirantis.com',
|
||||
keywords='web wsgi nailgun mirantis',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
install_requires=find_requires(),
|
||||
include_package_data=True,
|
||||
scripts=['manage.py'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'nailgun_syncdb = nailgun.db:syncdb',
|
||||
('nailgun_fixtures = '
|
||||
'nailgun.db.sqlalchemy.fixman:upload_fixtures'),
|
||||
'nailgund = nailgun.app:appstart',
|
||||
'assassind = nailgun.assassin.assassind:run',
|
||||
'receiverd = nailgun.rpc.receiverd:run',
|
||||
'statsenderd = nailgun.statistics.statsenderd:run',
|
||||
'oswl_collectord = nailgun.statistics.oswl.collector:run',
|
||||
('oswl_cleaner = nailgun.statistics.oswl.helpers:'
|
||||
'delete_expired_oswl_entries'),
|
||||
],
|
||||
'nailgun.extensions': [
|
||||
('cluster_upgrade = nailgun.extensions.cluster_upgrade'
|
||||
'.extension:ClusterUpgradeExtension'),
|
||||
('volume_manager = nailgun.extensions.volume_manager'
|
||||
'.extension:VolumeManagerExtension')
|
||||
],
|
||||
},
|
||||
data_files=recursive_data_files([('share/nailgun', 'static')]),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user