Switch to plugin base deployengines and serverproviders

This patch switch to plugin base and unify
deployengines and serverproviders plugins.

Co-authored-by: Olga Kopylova <olkonami@gmail.com>
Co-authored-by: Boris Pavlovic <boris@pavlovic.me>

Change-Id: I06fdef2a176266ab37325a421c10d386045cc2b9
This commit is contained in:
Boris Pavlovic 2015-06-09 14:34:28 +03:00
parent 21513a78ae
commit 6339d8c886
17 changed files with 59 additions and 136 deletions

View File

@ -262,10 +262,11 @@ class InfoCommands(object):
scenario_groups = list(set(s.split(".")[0] for s in scenarios))
scenario_methods = list(set(s.split(".")[1] for s in scenarios))
sla_info = [cls.get_name() for cls in sla.SLA.get_all()]
deploy_engines = [cls.get_name() for cls in utils.itersubclasses(
deploy.EngineFactory)]
server_providers = [cls.get_name() for cls in utils.itersubclasses(
serverprovider.ProviderFactory)]
deploy_engines = [cls.get_name() for cls in
deploy.EngineFactory.get_all()]
server_providers = [cls.get_name() for cls in
serverprovider.ProviderFactory.get_all()]
candidates = (scenarios + scenario_groups + scenario_methods +
sla_info + deploy_engines + server_providers)
suggestions = []
@ -339,24 +340,24 @@ class InfoCommands(object):
def _get_deploy_engine_info(self, query):
try:
deploy_engine = deploy.EngineFactory.get_by_name(query)
deploy_engine = deploy.EngineFactory.get(query)
header = "%s (deploy engine)" % deploy_engine.get_name()
info = self._make_header(header)
info += "\n\n"
info += utils.format_docstring(deploy_engine.__doc__)
return info
except exceptions.NoSuchEngine:
except exceptions.PluginNotFound:
return None
def _get_server_provider_info(self, query):
try:
server_provider = serverprovider.ProviderFactory.get_by_name(query)
server_provider = serverprovider.ProviderFactory.get(query)
header = "%s (server provider)" % server_provider.get_name()
info = self._make_header(header)
info += "\n\n"
info += utils.format_docstring(server_provider.__doc__)
return info
except exceptions.NoSuchVMProvider:
except exceptions.PluginNotFound:
return None
def _make_header(self, string):

View File

@ -20,6 +20,7 @@ import six
from rally.common.i18n import _
from rally.common import log as logging
from rally.common.plugin import plugin
from rally.common import utils
from rally import consts
from rally.deploy.serverprovider import provider
@ -29,8 +30,17 @@ from rally import exceptions
LOG = logging.getLogger(__name__)
def configure(name, namespace="default"):
return plugin.configure(name, namespace=namespace)
# FIXME(boris-42): We should make decomposition of this class.
# it should be called DeploymentManager
# and it should just manages server providers and engines
# engines class should have own base.
@six.add_metaclass(abc.ABCMeta)
class EngineFactory(object):
@configure("base_engine")
class EngineFactory(plugin.Plugin):
"""Base class of all deployment engines.
It's a base class with self-discovery of subclasses. Each subclass
@ -73,41 +83,25 @@ class EngineFactory(object):
if hasattr(self, "CONFIG_SCHEMA"):
jsonschema.validate(self.config, self.CONFIG_SCHEMA)
# FIXME(boris-42): Get rid of this method
def get_provider(self):
if "provider" in self.config:
return provider.ProviderFactory.get_provider(
self.config["provider"], self.deployment)
@staticmethod
def get_by_name(name):
"""Return Engine class by name."""
for engine in utils.itersubclasses(EngineFactory):
if name == engine.__name__:
return engine
raise exceptions.NoSuchEngine(engine_name=name)
# TODO(boris-42): Remove after switching to plugin base.
@classmethod
def get_name(cls):
return cls.__name__
# FIXME(boris-42): Get rid of this method
@staticmethod
def get_engine(name, deployment):
"""Returns instance of a deploy engine with corresponding name."""
try:
engine_cls = EngineFactory.get_by_name(name)
engine_cls = EngineFactory.get(name)
return engine_cls(deployment)
except exceptions.NoSuchEngine:
except exceptions.PluginNotFound:
LOG.error(_("Deployment %(uuid)s: Deploy engine for %(name)s "
"does not exist.") %
{"uuid": deployment["uuid"], "name": name})
deployment.update_status(consts.DeployStatus.DEPLOY_FAILED)
raise exceptions.NoSuchEngine(engine_name=name)
@staticmethod
def get_available_engines():
"""Returns a list of names of available engines."""
return [e.__name__ for e in utils.itersubclasses(EngineFactory)]
raise exceptions.PluginNotFound(engine_name=name)
@abc.abstractmethod
def deploy(self):

View File

@ -43,6 +43,7 @@ def get_updated_server(server, **kwargs):
return provider.Server.from_credentials(credentials)
@engine.configure(name="DevstackEngine")
class DevstackEngine(engine.EngineFactory):
"""Deploy Devstack cloud.

View File

@ -18,6 +18,7 @@ from rally.deploy import engine
from rally import objects
@engine.configure(name="ExistingCloud")
class ExistingCloud(engine.EngineFactory):
"""Just use an existing OpenStack deployment without deploying anything.

View File

@ -76,6 +76,7 @@ NETWORKS_SCHEMA = {
}
@engine.configure(name="FuelEngine")
class FuelEngine(engine.EngineFactory):
"""Deploy with FuelWeb.

View File

@ -36,6 +36,7 @@ def get_script_path(name):
"lxc", name)
@engine.configure(name="LxcEngine")
class LxcEngine(engine.EngineFactory):
"""Deploy with other engines in lxc containers.

View File

@ -24,6 +24,7 @@ from rally.deploy import engine
from rally import objects
@engine.configure(name="MultihostEngine")
class MultihostEngine(engine.EngineFactory):
"""Deploy multihost cloud with existing engines.

View File

@ -18,9 +18,9 @@ import abc
import jsonschema
import six
from rally.common.plugin import plugin
from rally.common import sshutils
from rally.common import utils
from rally import exceptions
class Server(utils.ImmutableMixin):
@ -91,8 +91,13 @@ class ResourceManager(object):
self.deployment.delete_resource(resource_id)
def configure(name, namespace="default"):
return plugin.configure(name, namespace=namespace)
@six.add_metaclass(abc.ABCMeta)
class ProviderFactory(object):
@configure(name="ProviderFactory")
class ProviderFactory(plugin.Plugin):
"""Base class of all server providers.
It's a base class with self-discovery of subclasses. Each subclass
@ -131,30 +136,13 @@ class ProviderFactory(object):
if hasattr(self, "CONFIG_SCHEMA"):
jsonschema.validate(self.config, self.CONFIG_SCHEMA)
@staticmethod
def get_by_name(name):
"""Return Server Provider class by type."""
for provider in utils.itersubclasses(ProviderFactory):
if name == provider.__name__:
return provider
raise exceptions.NoSuchVMProvider(vm_provider_name=name)
# TODO(boris-42): Remove after switching to plugin base.
@classmethod
def get_name(cls):
return cls.__name__
# FIXME(boris-42): Remove this method. And explicit create provider
@staticmethod
def get_provider(config, deployment):
"""Returns instance of server provider by name."""
provider_cls = ProviderFactory.get_by_name(config["type"])
provider_cls = ProviderFactory.get(config["type"])
return provider_cls(deployment, config)
@staticmethod
def get_available_providers():
"""Returns list of names of available engines."""
return [e.__name__ for e in utils.itersubclasses(ProviderFactory)]
@abc.abstractmethod
def create_servers(self, image_uuid=None, type_id=None, amount=1):
"""Create VMs with chosen image.

View File

@ -17,6 +17,7 @@
from rally.deploy.serverprovider import provider
@provider.configure(name="ExistingServers")
class ExistingServers(provider.ProviderFactory):
"""Just return endpoints from its own configuration.

View File

@ -252,6 +252,7 @@ class LxcHost(object):
yield self.get_server_object(name, wait)
@provider.configure(name="LxcProvider")
class LxcProvider(provider.ProviderFactory):
"""Provide lxc container(s) on given host.

View File

@ -34,6 +34,7 @@ SERVER_TYPE = "server"
KEYPAIR_TYPE = "keypair"
@provider.configure(name="OpenStackProvider")
class OpenStackProvider(provider.ProviderFactory):
"""Provide VMs using an existing OpenStack cloud.

View File

@ -23,6 +23,7 @@ import netaddr
from rally.deploy.serverprovider import provider
@provider.configure(name="VirshProvider")
class VirshProvider(provider.ProviderFactory):
"""Create VMs from prebuilt templates.

View File

@ -125,14 +125,6 @@ class PluginWithSuchNameExists(RallyException):
"`%(namespace)s` namespace")
class NoSuchEngine(NotFoundException):
msg_fmt = _("There is no engine with name `%(engine_name)s`.")
class NoSuchVMProvider(NotFoundException):
msg_fmt = _("There is no vm provider with name `%(vm_provider_name)s`.")
class NoSuchScenario(NotFoundException):
msg_fmt = _("There is no benchmark scenario with name `%(name)s`.")

View File

@ -43,10 +43,10 @@ class InfoCommandsTestCase(test.TestCase):
@mock.patch(SCENARIO + ".get_by_name",
return_value=dummy.Dummy)
def test_find_dummy_scenario_group(self, mock_get_by_name):
def test_find_dummy_scenario_group(self, mock_get):
query = "Dummy"
status = self.info.find(query)
mock_get_by_name.assert_called_once_with(query)
mock_get.assert_called_once_with(query)
self.assertIsNone(status)
@mock.patch(SCENARIO + ".get_scenario_by_name",
@ -72,20 +72,20 @@ class InfoCommandsTestCase(test.TestCase):
mock_get.assert_called_once_with(query)
self.assertIsNone(status)
@mock.patch(ENGINE + ".get_by_name",
@mock.patch(ENGINE + ".get",
return_value=existing_cloud.ExistingCloud)
def test_find_existing_cloud(self, mock_get_by_name):
def test_find_existing_cloud(self, mock_get):
query = "ExistingCloud"
status = self.info.find(query)
mock_get_by_name.assert_called_once_with(query)
mock_get.assert_called_once_with(query)
self.assertIsNone(status)
@mock.patch(PROVIDER + ".get_by_name",
@mock.patch(PROVIDER + ".get",
return_value=existing_servers.ExistingServers)
def test_find_existing_servers(self, mock_get_by_name):
def test_find_existing_servers(self, mock_get):
query = "ExistingServers"
status = self.info.find(query)
mock_get_by_name.assert_called_once_with(query)
mock_get.assert_called_once_with(query)
self.assertIsNone(status)
@mock.patch(COMMANDS + ".ServerProviders")

View File

@ -69,30 +69,10 @@ class ProviderTestCase(test.TestCase):
fake_validate.assert_called_once_with()
def test_get_provider_not_found(self):
self.assertRaises(exceptions.NoSuchVMProvider,
self.assertRaises(exceptions.PluginNotFound,
ProviderFactory.get_provider,
{"type": "fail"}, None)
def test_get_provider(self):
for p in FAKE_PROVIDERS:
p_inst = ProviderFactory.get_provider({"type": p.__name__},
None)
self.assertIsInstance(p_inst, p)
def test_get_by_name(self):
for p in FAKE_PROVIDERS:
self.assertEqual(p, ProviderFactory.get_by_name(p.__name__))
def test_get_by_name_not_found(self):
self.assertRaises(exceptions.NoSuchVMProvider,
ProviderFactory.get_by_name,
"NonExistingServers")
def test_get_available_providers(self):
providers = set([p.__name__ for p in FAKE_PROVIDERS])
real_providers = set(ProviderFactory.get_available_providers())
self.assertEqual(providers & real_providers, providers)
def test_vm_prvoider_factory_is_abstract(self):
self.assertRaises(TypeError, ProviderFactory)

View File

@ -55,6 +55,7 @@ class FakeDeployment(object):
pass
@deploy.configure(name="FakeEngine")
class FakeEngine(deploy.EngineFactory):
"""Fake deployment engine.
@ -83,36 +84,11 @@ class EngineMixIn(object):
pass
class EngineFake1(EngineMixIn, deploy.EngineFactory):
"""Fake deployment engine.
Used for tests.
"""
pass
class EngineFake2(EngineMixIn, deploy.EngineFactory):
"""Fake deployment engine.
Used for tests.
"""
pass
class EngineFake3(EngineFake2):
"""Fake deployment engine.
Used for tests.
"""
pass
class EngineFactoryTestCase(test.TestCase):
FAKE_ENGINES = [EngineFake1, EngineFake2, EngineFake3]
def test_get_engine_not_found(self):
deployment = make_fake_deployment()
self.assertRaises(exceptions.NoSuchEngine,
self.assertRaises(exceptions.PluginNotFound,
deploy.EngineFactory.get_engine,
"non_existing_engine", deployment)
self.assertEqual(consts.DeployStatus.DEPLOY_FAILED,
@ -228,26 +204,9 @@ class EngineFactoryTestCase(test.TestCase):
def test_get_engine(self):
deployment = make_fake_deployment()
engines = EngineFactoryTestCase.FAKE_ENGINES
for e in engines:
engine_inst = deploy.EngineFactory.get_engine(e.__name__,
deployment)
self.assertIsInstance(engine_inst, e)
def test_get_by_name(self):
engines = EngineFactoryTestCase.FAKE_ENGINES
for e in engines:
self.assertEqual(e, deploy.EngineFactory.get_by_name(e.__name__))
def test_get_by_name_not_found(self):
self.assertRaises(exceptions.NoSuchEngine,
deploy.EngineFactory.get_by_name,
"NonExistingEngine")
def test_get_available_engines(self):
engines = set([e.__name__ for e in EngineFactoryTestCase.FAKE_ENGINES])
real_engines = set(deploy.EngineFactory.get_available_engines())
self.assertEqual(engines & real_engines, engines)
engine_inst = deploy.EngineFactory.get_engine("FakeEngine",
deployment)
self.assertIsInstance(engine_inst, FakeEngine)
def test_engine_factory_is_abstract(self):
self.assertRaises(TypeError, deploy.EngineFactory)

View File

@ -80,11 +80,11 @@ class DocstringsTestCase(test.TestCase):
long_description=False)
def test_all_deploy_engines_have_docstrings(self):
for deploy_engine in utils.itersubclasses(deploy.EngineFactory):
for deploy_engine in deploy.EngineFactory.get_all():
self._assert_class_has_docstrings(deploy_engine)
def test_all_server_providers_have_docstrings(self):
for provider in utils.itersubclasses(serverprovider.ProviderFactory):
for provider in serverprovider.ProviderFactory.get_all():
self._assert_class_has_docstrings(provider)
def test_all_SLA_have_docstrings(self):