[envs] Introduce Env Manager & Platforms (part 1)
EnvManager is one of key Rally components, It is actually replacment for Rally deployment component. It manages and stores information about tested platforms. Every Env has: - unique name and UUID - dates when it was created and updated - plugins spec and data - platforms names and data Comparing to Rally deployment, env manager is way simpler and flat. EnvManager manage Platforms (which are plugins) However, even if it is simpler it allows to do way more flexible things like manage multiple platforms in single env. This patch: - Introduce EnvManager - Intorudce Platform plugin base - Intorduce DB layer for Envs & Platforms - Intrdouce 3 Exceptions: -- ManagerException ---- ManagerInvalidSpec ---- ManagerInvalidState Change-Id: Ide95c8b1e8e72293c009f4cd5e430e64eb1dd604
This commit is contained in:
parent
c4dd2d6e56
commit
4719c5d8c7
@ -362,6 +362,67 @@ def resource_delete(id):
|
||||
return get_impl().resource_delete(id)
|
||||
|
||||
|
||||
def env_get(uuid_or_name):
|
||||
"""Returns envs with corresponding uuid or name."""
|
||||
return get_impl().env_get(uuid_or_name)
|
||||
|
||||
|
||||
def env_get_status(uuid):
|
||||
"""Returns status of env with corresponding uuid."""
|
||||
return get_impl().env_get_status(uuid)
|
||||
|
||||
|
||||
def env_list(status=None):
|
||||
"""Return list of envs, filtered by status, if status provided."""
|
||||
return get_impl().env_list(status=status)
|
||||
|
||||
|
||||
def env_create(name, status, description, extras, spec, platforms):
|
||||
"""Created db record of env and platforms."""
|
||||
return get_impl().env_create(
|
||||
name, status, description, extras, spec, platforms)
|
||||
|
||||
|
||||
def env_rename(uuid, old_name, new_name):
|
||||
"""Renames env. Returns op result as bool"""
|
||||
return get_impl().env_rename(uuid, old_name, new_name)
|
||||
|
||||
|
||||
def env_update(uuid, description=None, extras=None):
|
||||
"""Update description and extra of envs. Returns op result as bool."""
|
||||
return get_impl().env_update(uuid, description=description, extras=extras)
|
||||
|
||||
|
||||
def env_set_status(uuid, old_status, new_status):
|
||||
"""Set new env status. """
|
||||
return get_impl().env_set_status(uuid, old_status, new_status)
|
||||
|
||||
|
||||
def env_delete_cascade(uuid):
|
||||
"""Delete envs, platforms and all related to env resources."""
|
||||
return get_impl().env_delete_cascade(uuid)
|
||||
|
||||
|
||||
def platforms_list(env_uuid):
|
||||
"""List platforms related to some env."""
|
||||
return get_impl().platforms_list(env_uuid)
|
||||
|
||||
|
||||
def platform_get(uuid):
|
||||
"""Returns platforms with corresponding uuid."""
|
||||
return get_impl().platform_get(uuid)
|
||||
|
||||
|
||||
def platform_set_status(uuid, old_status, new_status):
|
||||
"""Set's new status to platform"""
|
||||
return get_impl().platform_set_status(uuid, old_status, new_status)
|
||||
|
||||
|
||||
def platform_set_data(uuid, platform_data=None, plugin_data=None):
|
||||
"""Set's platform data."""
|
||||
return get_impl().platform_set_data(uuid, platform_data, plugin_data)
|
||||
|
||||
|
||||
def verifier_create(name, vtype, platform, source, version, system_wide,
|
||||
extra_settings=None):
|
||||
"""Create a verifier record.
|
||||
|
@ -206,9 +206,6 @@ class Connection(object):
|
||||
:raises Exception: when the model is not a sublcass of
|
||||
:class:`rally.common.db.sqlalchemy.models.RallyBase`.
|
||||
"""
|
||||
session = session or get_session()
|
||||
query = session.query(model)
|
||||
|
||||
def issubclassof_rally_base(obj):
|
||||
return isinstance(obj, type) and issubclass(obj, models.RallyBase)
|
||||
|
||||
@ -216,7 +213,8 @@ class Connection(object):
|
||||
raise exceptions.DBException(
|
||||
"The model %s should be a subclass of RallyBase" % model)
|
||||
|
||||
return query
|
||||
session = session or get_session()
|
||||
return session.query(model)
|
||||
|
||||
def _tags_get(self, uuid, tag_type, session=None):
|
||||
tags = (self.model_query(models.Tag, session=session).
|
||||
@ -571,6 +569,143 @@ class Connection(object):
|
||||
session.query(models.Subtask).filter_by(uuid=subtask_uuid).update(
|
||||
subtask_values)
|
||||
|
||||
@serialize
|
||||
def env_get(self, uuid_or_name):
|
||||
env = (self.model_query(models.Env)
|
||||
.filter(sa.or_(models.Env.uuid == uuid_or_name,
|
||||
models.Env.name == uuid_or_name))
|
||||
.first())
|
||||
if not env:
|
||||
raise exceptions.DBRecordNotFound(
|
||||
criteria="uuid or name is %s" % uuid_or_name, table="envs")
|
||||
return env
|
||||
|
||||
@serialize
|
||||
def env_get_status(self, uuid):
|
||||
resp = (self.model_query(models.Env)
|
||||
.filter_by(uuid=uuid)
|
||||
.options(sa.orm.load_only("status"))
|
||||
.first())
|
||||
if not resp:
|
||||
raise exceptions.DBRecordNotFound(
|
||||
criteria="uuid: %s" % uuid, table="envs")
|
||||
return resp["status"]
|
||||
|
||||
@serialize
|
||||
def env_list(self, status=None):
|
||||
query = self.model_query(models.Env)
|
||||
if status:
|
||||
query = query.filter_by(status=status)
|
||||
return query.all()
|
||||
|
||||
@serialize
|
||||
def env_create(self, name, status, description, extras, spec, platforms):
|
||||
try:
|
||||
env_uuid = models.UUID()
|
||||
for p in platforms:
|
||||
p["env_uuid"] = env_uuid
|
||||
|
||||
env = models.Env(
|
||||
name=name, uuid=env_uuid,
|
||||
status=status, description=description,
|
||||
extras=extras, spec=spec
|
||||
)
|
||||
# db_session.add(env)
|
||||
get_session().bulk_save_objects([env] + [
|
||||
models.Platform(**p) for p in platforms
|
||||
])
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exceptions.DBRecordExists(
|
||||
field="name", value=name, table="envs")
|
||||
|
||||
return self.env_get(env_uuid)
|
||||
|
||||
def env_rename(self, uuid, old_name, new_name):
|
||||
try:
|
||||
return bool(self.model_query(models.Env)
|
||||
.filter_by(uuid=uuid, name=old_name)
|
||||
.update({"name": new_name}))
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exceptions.DBRecordExists(
|
||||
field="name", value=new_name, table="envs")
|
||||
|
||||
def env_update(self, uuid, description=None, extras=None):
|
||||
values = {}
|
||||
if description is not None:
|
||||
values["description"] = description
|
||||
if extras is not None:
|
||||
values["extras"] = extras
|
||||
|
||||
if not values:
|
||||
return True
|
||||
|
||||
return bool(self.model_query(models.Env)
|
||||
.filter_by(uuid=uuid)
|
||||
.update(values))
|
||||
|
||||
def env_set_status(self, uuid, old_status, new_status):
|
||||
count = (self.model_query(models.Env)
|
||||
.filter_by(uuid=uuid, status=old_status)
|
||||
.update({"status": new_status}))
|
||||
if count:
|
||||
return True
|
||||
|
||||
raise exceptions.DBConflict(
|
||||
"Env %s should be in status %s actual %s"
|
||||
% (uuid, old_status, self.env_get_status(uuid)))
|
||||
|
||||
def env_delete_cascade(self, uuid):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
(self.model_query(models.Platform, session=session)
|
||||
.filter_by(env_uuid=uuid)
|
||||
.delete())
|
||||
(self.model_query(models.Env, session=session)
|
||||
.filter_by(uuid=uuid)
|
||||
.delete())
|
||||
# NOTE(boris-42): Add queries to delete corresponding
|
||||
# task and verify results, when they switch to Env
|
||||
|
||||
@serialize
|
||||
def platforms_list(self, env_uuid):
|
||||
return (self.model_query(models.Platform)
|
||||
.filter_by(env_uuid=env_uuid)
|
||||
.all())
|
||||
|
||||
@serialize
|
||||
def platform_get(self, uuid):
|
||||
p = self.model_query(models.Platform).filter_by(uuid=uuid).first()
|
||||
if not p:
|
||||
raise exceptions.DBRecordNotFound(
|
||||
criteria="uuid = %s" % uuid, table="platforms")
|
||||
return p
|
||||
|
||||
def platform_set_status(self, uuid, old_status, new_status):
|
||||
count = (self.model_query(models.Platform)
|
||||
.filter_by(uuid=uuid, status=old_status)
|
||||
.update({"status": new_status}))
|
||||
if count:
|
||||
return True
|
||||
|
||||
platform = self.platform_get(uuid)
|
||||
raise exceptions.DBConflict(
|
||||
"Platform %s should be in status %s actual %s"
|
||||
% (uuid, old_status, platform["status"]))
|
||||
|
||||
def platform_set_data(self, uuid, platform_data=None, plugin_data=None):
|
||||
values = {}
|
||||
if platform_data is not None:
|
||||
values["platform_data"] = platform_data
|
||||
if plugin_data is not None:
|
||||
values["plugin_data"] = plugin_data
|
||||
|
||||
if not values:
|
||||
return True
|
||||
|
||||
return bool(self.model_query(models.Platform)
|
||||
.filter_by(uuid=uuid)
|
||||
.update(values))
|
||||
|
||||
def _deployment_get(self, deployment, session=None):
|
||||
stored_deployment = self.model_query(
|
||||
models.Deployment,
|
||||
|
@ -0,0 +1,86 @@
|
||||
# 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.
|
||||
|
||||
"""Add Env & Platforms tables
|
||||
|
||||
Revision ID: a43700a813a5
|
||||
Revises: dc46687661df
|
||||
Create Date: 2017-12-27 13:37:10.144970
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from rally.common.db.sqlalchemy import types as sa_types
|
||||
from rally import exceptions
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "a43700a813a5"
|
||||
down_revision = "44169f4d455e"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"envs",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("uuid", sa.String(36), nullable=False),
|
||||
|
||||
sa.Column("name", sa.String(255), nullable=False),
|
||||
sa.Column("description", sa.Text, default=""),
|
||||
sa.Column("status", sa.String(36), nullable=False),
|
||||
|
||||
sa.Column("extras", sa_types.MutableJSONEncodedDict, default={}),
|
||||
sa.Column("spec", sa_types.MutableJSONEncodedDict, default={}),
|
||||
|
||||
sa.Column("created_at", sa.DateTime),
|
||||
sa.Column("updated_at", sa.DateTime)
|
||||
)
|
||||
|
||||
op.create_index("env_uuid", "envs", ["uuid"], unique=True)
|
||||
op.create_index("env_name", "envs", ["name"], unique=True)
|
||||
op.create_index("env_status", "envs", ["status"])
|
||||
|
||||
op.create_table(
|
||||
"platforms",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("uuid", sa.String(36), nullable=False),
|
||||
sa.Column("env_uuid", sa.String(36), nullable=False),
|
||||
|
||||
sa.Column("status", sa.String(36), nullable=False),
|
||||
|
||||
sa.Column("plugin_name", sa.String(36), nullable=False),
|
||||
sa.Column("plugin_spec", sa_types.MutableJSONEncodedDict,
|
||||
nullable=False),
|
||||
sa.Column("plugin_data", sa_types.MutableJSONEncodedDict,
|
||||
default={}),
|
||||
|
||||
sa.Column("platform_name", sa.String(36)),
|
||||
sa.Column("platform_data", sa_types.MutableJSONEncodedDict,
|
||||
default={}),
|
||||
|
||||
sa.Column("created_at", sa.DateTime),
|
||||
sa.Column("updated_at", sa.DateTime),
|
||||
|
||||
)
|
||||
|
||||
op.create_index("platform_uuid", "platforms", ["uuid"], unique=True)
|
||||
op.create_index("platform_env_uuid", "platforms", ["env_uuid"])
|
||||
|
||||
|
||||
def downgrade():
|
||||
raise exceptions.DowngradeNotSupported()
|
@ -136,6 +136,47 @@ class Resource(BASE, RallyBase):
|
||||
)
|
||||
|
||||
|
||||
class Env(BASE, RallyBase):
|
||||
"""Represent a environment."""
|
||||
__tablename__ = "envs"
|
||||
__table_args__ = (
|
||||
sa.Index("env_uuid", "uuid", unique=True),
|
||||
sa.Index("env_name", "name", unique=True),
|
||||
sa.Index("env_status", "status")
|
||||
)
|
||||
|
||||
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
||||
uuid = sa.Column(sa.String(36), default=UUID, nullable=False)
|
||||
name = sa.Column(sa.String(255), nullable=False)
|
||||
description = sa.Column(sa.Text, default="")
|
||||
status = sa.Column(sa.String(36), nullable=False)
|
||||
extras = sa.Column(sa_types.MutableJSONEncodedDict, default={})
|
||||
spec = sa.Column(sa_types.MutableJSONEncodedDict, default={})
|
||||
|
||||
|
||||
class Platform(BASE, RallyBase):
|
||||
"""Represent environment's platforms."""
|
||||
__tablename__ = "platforms"
|
||||
__table_args__ = (
|
||||
sa.Index("platform_uuid", "uuid", unique=True),
|
||||
sa.Index("platform_env_uuid", "env_uuid")
|
||||
)
|
||||
|
||||
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
||||
uuid = sa.Column(sa.String(36), default=UUID, nullable=False)
|
||||
env_uuid = sa.Column(sa.String(36), nullable=False)
|
||||
|
||||
status = sa.Column(sa.String(36), nullable=False)
|
||||
|
||||
plugin_name = sa.Column(sa.String(36), nullable=False)
|
||||
plugin_spec = sa.Column(sa_types.MutableJSONEncodedDict, default={},
|
||||
nullable=False)
|
||||
plugin_data = sa.Column(sa_types.MutableJSONEncodedDict, default={})
|
||||
|
||||
platform_name = sa.Column(sa.String(36))
|
||||
platform_data = sa.Column(sa_types.MutableJSONEncodedDict, default={})
|
||||
|
||||
|
||||
class Task(BASE, RallyBase):
|
||||
"""Represents a task."""
|
||||
__tablename__ = "tasks"
|
||||
|
0
rally/env/__init__.py
vendored
Normal file
0
rally/env/__init__.py
vendored
Normal file
578
rally/env/env_mgr.py
vendored
Normal file
578
rally/env/env_mgr.py
vendored
Normal file
@ -0,0 +1,578 @@
|
||||
# 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 copy
|
||||
import sys
|
||||
|
||||
import jsonschema
|
||||
|
||||
from rally.common import db
|
||||
from rally.common import logging
|
||||
from rally.common import utils
|
||||
from rally.env import platform
|
||||
from rally import exceptions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _EnvStatus(utils.ImmutableMixin, utils.EnumMixin):
|
||||
"""Rally Env Statuses."""
|
||||
|
||||
INIT = "INITIALIZING"
|
||||
|
||||
READY = "READY"
|
||||
FAILED_TO_CREATE = "FAILED TO CREATE"
|
||||
|
||||
CLEANING = "CLEANING"
|
||||
|
||||
DESTROYING = "DESTROYING"
|
||||
FAILED_TO_DESTROY = "FAILED TO DESTROY"
|
||||
DESTROYED = "DESTROYED"
|
||||
|
||||
TRANSITION_TABLE = {
|
||||
INIT: (READY, FAILED_TO_CREATE),
|
||||
READY: (DESTROYING, CLEANING),
|
||||
CLEANING: (READY, ),
|
||||
FAILED_TO_CREATE: (DESTROYING, ),
|
||||
DESTROYING: (DESTROYED, FAILED_TO_DESTROY),
|
||||
FAILED_TO_DESTROY: (DESTROYING, )
|
||||
}
|
||||
|
||||
|
||||
STATUS = _EnvStatus()
|
||||
|
||||
|
||||
class EnvManager(object):
|
||||
"""Implements life cycle management of Rally Envs.
|
||||
|
||||
|
||||
EnvManager is one of key Rally components,
|
||||
It manages and stores information about tested platforms. Every Env has:
|
||||
- unique name and UUID
|
||||
- dates when it was created and updated
|
||||
- platform plugins spec and data
|
||||
- platform data
|
||||
|
||||
|
||||
Env Input has next syntax:
|
||||
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"description": "Env version"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "User specific description of deployment"
|
||||
},
|
||||
"extras": {
|
||||
"type": "object",
|
||||
"description": "Custom dict with data"
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"*": {
|
||||
"type": "object",
|
||||
"*": {},
|
||||
"description": |
|
||||
Keys are option's name, values are option's values
|
||||
},
|
||||
"description": "Keys are groups, values are options names"
|
||||
}
|
||||
},
|
||||
"platforms": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"*": {
|
||||
"type": "object",
|
||||
"description": |
|
||||
Key is platform plugin name,
|
||||
values are plugin arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Env.data property is dict that is consumed by other rally components,
|
||||
like task, verify and maybe something else in future.
|
||||
|
||||
{
|
||||
"name": {"type": "string"},
|
||||
"status": {"type": "string"},
|
||||
"description: {"type": "string"},
|
||||
"extras": {"type": "object"},
|
||||
"config": {"type": "object"},
|
||||
"platforms": {
|
||||
"type": "object",
|
||||
"*": {
|
||||
"type": "object"
|
||||
"description": "Key is platform name, value is platform data"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, _data):
|
||||
"""Private method to initializes env manager.
|
||||
|
||||
THis method is not meant to be called directly, use one of
|
||||
next class methods: get(), create() or list().
|
||||
"""
|
||||
self._env = _data
|
||||
self.uuid = self._env["uuid"]
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""Returns current state of Env that was fetched from DB."""
|
||||
return db.env_get_status(self.uuid)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""Returns full information about env including platforms."""
|
||||
self._env = db.env_get(self.uuid)
|
||||
return {
|
||||
"id": self._env["id"],
|
||||
"uuid": self._env["uuid"],
|
||||
"created_at": self._env["created_at"],
|
||||
"updated_at": self._env["updated_at"],
|
||||
"name": self._env["name"],
|
||||
"description": self._env["description"],
|
||||
"status": self._env["status"],
|
||||
"spec": copy.deepcopy(self._env["spec"]),
|
||||
"extras": copy.deepcopy(self._env["extras"]),
|
||||
"platforms": db.platforms_list(self.uuid)
|
||||
}
|
||||
|
||||
def _get_platforms(self):
|
||||
"""Iterate over Envs platforms.
|
||||
|
||||
:returns: Generator that returns list of tuples
|
||||
(uuid, instance of rally.env.platform.Platform)
|
||||
"""
|
||||
raw_platforms = db.platforms_list(self.uuid)
|
||||
platforms = []
|
||||
|
||||
for p in raw_platforms:
|
||||
plugin_cls = platform.Platform.get(p["plugin_name"])
|
||||
platforms.append(
|
||||
plugin_cls(
|
||||
p["plugin_spec"],
|
||||
uuid=p["uuid"],
|
||||
plugin_data=p["plugin_data"],
|
||||
platform_data=p["platform_data"],
|
||||
status=p["status"]
|
||||
)
|
||||
)
|
||||
|
||||
return platforms
|
||||
|
||||
@classmethod
|
||||
def get(cls, uuid_or_name):
|
||||
"""Get the instance of EnvManager by uuid or name.
|
||||
|
||||
:param uuid_or_name: Returns record that has uuid or name equal to it.
|
||||
:returns: Instance of rally.env.env_mgr.EnvManager
|
||||
"""
|
||||
return cls(db.env_get(uuid_or_name))
|
||||
|
||||
@classmethod
|
||||
def list(cls, status=None):
|
||||
"""Returns list of instances of EnvManagers."""
|
||||
return [cls(data) for data in db.env_list(status=status)]
|
||||
|
||||
@classmethod
|
||||
def _validate_and_create_env(cls, name, description, extras, spec):
|
||||
"""Validated and create env and platforms DB records.
|
||||
|
||||
Do NOT use this method directly. Call create() method instead.
|
||||
|
||||
- Restore full name of plugin. If only platform name is specified
|
||||
plugin with name existing@<platform_name> is going to be used
|
||||
- Validates spec using standard plugin validation mechanism
|
||||
- Creates env and platforms DB records in DB
|
||||
|
||||
:returns: dict that contains env record stored in DB
|
||||
"""
|
||||
for p_name, p_spec in spec.items():
|
||||
if "@" not in p_name:
|
||||
spec["existing@%s" % p_name] = p_spec
|
||||
spec.pop(p_name)
|
||||
|
||||
errors = []
|
||||
for p_name, p_spec in spec.items():
|
||||
errors.extend(platform.Platform.validate(p_name, {}, spec, p_spec))
|
||||
if errors:
|
||||
raise exceptions.ManagerInvalidSpec(
|
||||
mgr="Env", spec=spec, errors=errors)
|
||||
|
||||
_platforms = []
|
||||
for p_name, p_spec in spec.items():
|
||||
_platforms.append({
|
||||
"status": platform.STATUS.INIT,
|
||||
"plugin_name": p_name,
|
||||
"plugin_spec": p_spec,
|
||||
"platform_name": p_name.split("@")[1]
|
||||
})
|
||||
|
||||
return cls(db.env_create(name, STATUS.INIT, description, extras,
|
||||
spec, _platforms))
|
||||
|
||||
def _create_platforms(self):
|
||||
"""Iterates over platform and creates them, storing results in DB.
|
||||
|
||||
Do NOT use this method directly! Use create() instead.
|
||||
|
||||
All platform statuses are going to be updated.
|
||||
- If everything is OK all platforms and env would have READY statuts.
|
||||
- If some of platforms failed, it will get status "FAILED TO CREATE"
|
||||
as well as Env, all following platforms would have "SKIPPED" state.
|
||||
- If there are issues with DB, and we can't store results to DB,
|
||||
platform will be destroyed so we won't keep messy env, everything
|
||||
will be logged.
|
||||
|
||||
This is not ideal solution, but it's best that we can do at the moment
|
||||
"""
|
||||
new_env_status = STATUS.READY
|
||||
|
||||
for p in self._get_platforms():
|
||||
if new_env_status != STATUS.READY:
|
||||
db.platform_set_status(
|
||||
p.uuid, platform.STATUS.INIT, platform.STATUS.SKIPPED)
|
||||
continue
|
||||
|
||||
try:
|
||||
platform_data, plugin_data = p.create()
|
||||
except Exception:
|
||||
new_env_status = STATUS.FAILED_TO_CREATE
|
||||
LOG.exception(
|
||||
"Failed to create platform (%(uuid)s): "
|
||||
"%(name)s with spec: %(spec)s" %
|
||||
{"uuid": p.uuid, "name": p.get_fullname(), "spec": p.spec})
|
||||
try:
|
||||
db.platform_set_status(p.uuid, platform.STATUS.INIT,
|
||||
platform.STATUS.FAILED_TO_CREATE)
|
||||
except Exception:
|
||||
LOG.Exception(
|
||||
"Failed to set platform %(uuid)s status %(status)s"
|
||||
% {"uuid": p.uuid,
|
||||
"status": platform.STATUS.FAILED_TO_CREATE})
|
||||
|
||||
if new_env_status == STATUS.FAILED_TO_CREATE:
|
||||
continue
|
||||
|
||||
try:
|
||||
db.platform_set_data(
|
||||
p.uuid,
|
||||
platform_data=platform_data, plugin_data=plugin_data)
|
||||
db.platform_set_status(
|
||||
p.uuid, platform.STATUS.INIT, platform.STATUS.READY)
|
||||
except Exception:
|
||||
new_env_status = STATUS.FAILED_TO_CREATE
|
||||
|
||||
# NOTE(boris-42): We can't store platform data, because of
|
||||
# issues with DB, to keep env clean we must
|
||||
# destroy platform while we have complete data.
|
||||
p.status = platform.STATUS.FAILED_TO_CREATE
|
||||
p.platform_data, p.plugin_data = platform_data, plugin_data
|
||||
try:
|
||||
p.destroy()
|
||||
LOG.warrning("Couldn't store platform %s data to DB."
|
||||
"Attempt to destroy it succeeded." % p.uuid)
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
"Couldn't store data of platform(%(uuid)s): %(name)s "
|
||||
"with spec: %(spec)s. Attempt to destroy it failed. "
|
||||
"Sorry, but we can't do anything else for you. :("
|
||||
% {"uuid": p.uuid,
|
||||
"name": p.get_fullname(),
|
||||
"spec": p.spec})
|
||||
|
||||
db.env_set_status(self.uuid, STATUS.INIT, new_env_status)
|
||||
|
||||
@classmethod
|
||||
def create(cls, name, description, extras, spec):
|
||||
"""Creates DB record for new env and returns instance of Env class.
|
||||
|
||||
:param name: User specified name of env
|
||||
:param description: User specified description
|
||||
:param extras: User specified dict with extra options
|
||||
:param spec: Specification that contains info about all
|
||||
platform plugins and their arguments.
|
||||
:returns: EnvManager instance corresponding to created Env
|
||||
"""
|
||||
self = cls._validate_and_create_env(name, description, extras, spec)
|
||||
self._create_platforms()
|
||||
return self
|
||||
|
||||
def rename(self, new_name):
|
||||
"""Renames env record.
|
||||
|
||||
:param new_name: New Env name.
|
||||
"""
|
||||
if self._env["name"] == new_name:
|
||||
return True
|
||||
return db.env_rename(self.uuid, self._env["name"], new_name)
|
||||
|
||||
def update(self, description=None, extras=None):
|
||||
"""Update description and extras for environment.
|
||||
|
||||
:param description: New description for env
|
||||
:param extras: New extras for env
|
||||
"""
|
||||
values = {}
|
||||
|
||||
if description and description != self._env["description"]:
|
||||
values["description"] = description
|
||||
if extras and extras != self._env["extras"]:
|
||||
values["extras"] = extras
|
||||
|
||||
if values:
|
||||
return db.env_update(self.uuid, **values)
|
||||
return True
|
||||
|
||||
def update_spec(self, new_spec):
|
||||
"""Update env spec. [not implemented]"""
|
||||
# NOTE(boris-42): This functionality requires proper implementation of
|
||||
# state machine and journal execution, which we are
|
||||
# going to implement later for all code base
|
||||
raise NotImplementedError()
|
||||
|
||||
_HEALTH_FORMAT = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"available": {"type": "boolean"},
|
||||
"message": {"type": "string"},
|
||||
"traceback": {"type": "array", "minItems": 3, "maxItems": 3}
|
||||
},
|
||||
"required": ["available"],
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
def check_health(self):
|
||||
"""Iterates over all platforms in env and returns their health.
|
||||
|
||||
Format of result is
|
||||
{
|
||||
"platform_name": {
|
||||
"available": True/False,
|
||||
"message": "custom message"},
|
||||
"traceback": ...
|
||||
}
|
||||
|
||||
:return: Dict with results
|
||||
"""
|
||||
result = {}
|
||||
|
||||
for p in self._get_platforms():
|
||||
try:
|
||||
check_result = p.check_health()
|
||||
jsonschema.validate(check_result, self._HEALTH_FORMAT)
|
||||
check_result.setdefault("message", "OK!")
|
||||
except Exception as e:
|
||||
msg = ("Plugin %s.check_health() method is broken"
|
||||
% p.get_fullname())
|
||||
LOG.exception(msg)
|
||||
check_result = {"message": msg, "available": False}
|
||||
if not isinstance(e, jsonschema.ValidationError):
|
||||
check_result["traceback"] = sys.exc_info()
|
||||
|
||||
result[p.get_fullname()] = check_result
|
||||
|
||||
return result
|
||||
|
||||
_INFO_FORMAT = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {},
|
||||
"error": {"type": "string"},
|
||||
"traceback": {"type": "array", "minItems": 3, "maxItems": 3},
|
||||
},
|
||||
"required": ["info"],
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
def get_info(self):
|
||||
"""Get detailed information about all platforms.
|
||||
|
||||
Platform plugins may collect any information from plugin and return
|
||||
it back as a dict.
|
||||
"""
|
||||
result = {}
|
||||
|
||||
for p in self._get_platforms():
|
||||
try:
|
||||
info = p.info()
|
||||
jsonschema.validate(info, self._INFO_FORMAT)
|
||||
except Exception as e:
|
||||
msg = "Plugin %s.info() method is broken" % p.get_fullname()
|
||||
LOG.exception(msg)
|
||||
info = {"info": None, "error": msg}
|
||||
if not isinstance(e, jsonschema.ValidationError):
|
||||
info["traceback"] = sys.exc_info()
|
||||
|
||||
result[p.get_fullname()] = info
|
||||
|
||||
return result
|
||||
|
||||
_CLEANUP_FORMAT = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"discovered": {"type": "integer"},
|
||||
"deleted": {"type": "integer"},
|
||||
"failed": {"type": "integer"},
|
||||
"resources": {
|
||||
"*": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"discovered": {"type": "integer"},
|
||||
"deleted": {"type": "integer"},
|
||||
"failed": {"type": "integer"}
|
||||
},
|
||||
"required": ["discovered", "deleted", "failed"],
|
||||
"additionalProperties": False
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"resource_id": {"type": "string"},
|
||||
"resource_type": {"type": "string"},
|
||||
"message": {"type": "string"},
|
||||
"traceback": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3
|
||||
}
|
||||
},
|
||||
"required": ["message"],
|
||||
"additionalProperties": False
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["discovered", "deleted", "failed", "resources", "errors"],
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
def cleanup(self, task_uuid=None):
|
||||
"""Cleans all platform in env.
|
||||
|
||||
:param task_uuid: Cleans up only resources of specific task.
|
||||
:returns: Dict with status of all cleanups
|
||||
"""
|
||||
db.env_set_status(self.uuid, STATUS.READY, STATUS.CLEANING)
|
||||
|
||||
result = {}
|
||||
for p in self._get_platforms():
|
||||
try:
|
||||
cleanup_info = p.cleanup(task_uuid)
|
||||
jsonschema.validate(cleanup_info, self._CLEANUP_FORMAT)
|
||||
except Exception as e:
|
||||
msg = "Plugin %s.cleanup() method is broken" % p.get_fullname()
|
||||
LOG.exception(msg)
|
||||
cleanup_info = {
|
||||
"discovered": 0, "deleted": 0, "failed": 0,
|
||||
"resources": {}, "errors": [{"message": msg}]
|
||||
}
|
||||
if not isinstance(e, jsonschema.ValidationError):
|
||||
cleanup_info["errors"][0]["traceback"] = sys.exc_info()
|
||||
|
||||
result[p.get_fullname()] = cleanup_info
|
||||
|
||||
db.env_set_status(self.uuid, STATUS.CLEANING, STATUS.READY)
|
||||
return result
|
||||
|
||||
def destroy(self, skip_cleanup=False):
|
||||
"""Destroys all platforms related to env.
|
||||
|
||||
:param skip_cleanup: By default, before destroying plaform it's cleaned
|
||||
"""
|
||||
cleanup_info = {"skipped": True}
|
||||
if not skip_cleanup:
|
||||
cleanup_info = self.cleanup()
|
||||
cleanup_info["skipped"] = False
|
||||
if cleanup_info["errors"]:
|
||||
return {
|
||||
"cleanup_info": cleanup_info,
|
||||
"destroy_info": {
|
||||
"skipped": True,
|
||||
"platforms": {},
|
||||
"message": "Skipped because cleanup failed"
|
||||
}
|
||||
}
|
||||
|
||||
result = {
|
||||
"cleanup_info": cleanup_info,
|
||||
"destroy_info": {
|
||||
"skipped": False,
|
||||
"platforms": {}
|
||||
}
|
||||
}
|
||||
|
||||
db.env_set_status(self.uuid, STATUS.READY, STATUS.DESTROYING)
|
||||
|
||||
platforms = result["destroy_info"]["platforms"]
|
||||
new_env_status = STATUS.DESTROYED
|
||||
|
||||
for p in self._get_platforms():
|
||||
name = p.get_fullname()
|
||||
platforms[name] = {"status": {"old": p.status}}
|
||||
if p.status == platform.STATUS.DESTROYED:
|
||||
platforms[name]["status"]["new"] = p.status
|
||||
platforms[name]["message"] = (
|
||||
"Platform is already destroyed. Do nothing")
|
||||
continue
|
||||
|
||||
db.platform_set_status(
|
||||
p.uuid, p.status, platform.STATUS.DESTROYING)
|
||||
try:
|
||||
p.destroy()
|
||||
except Exception:
|
||||
db.platform_set_status(p.uuid,
|
||||
platform.STATUS.DESTROYING,
|
||||
platform.STATUS.FAILED_TO_DESTROY)
|
||||
platforms[name]["message"] = "Failed to destroy"
|
||||
platforms[name]["status"]["new"] = (
|
||||
platform.STATUS.FAILED_TO_DESTROY)
|
||||
platforms[name]["traceback"] = sys.exc_info()
|
||||
new_env_status = STATUS.FAILED_TO_DESTROY
|
||||
else:
|
||||
db.platform_set_status(p.uuid,
|
||||
platform.STATUS.DESTROYING,
|
||||
platform.STATUS.DESTROYED)
|
||||
platforms[name]["message"] = "Successfully destroyed"
|
||||
platforms[name]["status"]["new"] = platform.STATUS.DESTROYED
|
||||
|
||||
db.env_set_status(self.uuid, STATUS.DESTROYING, new_env_status)
|
||||
|
||||
return result
|
||||
|
||||
def delete(self, force=False):
|
||||
"""Cascade delete of DB records related to env.
|
||||
|
||||
It deletes all Task and Verify results related to this env as well.
|
||||
|
||||
:param Force: Use it if you don't want to perform status check
|
||||
"""
|
||||
_status = self.status
|
||||
if not force and _status != STATUS.DESTROYED:
|
||||
raise exceptions.ManagerInvalidState(
|
||||
mgr="Env", expected=STATUS.DESTROYED, actual=_status)
|
||||
db.env_delete_cascade(self.uuid)
|
113
rally/env/platform.py
vendored
Normal file
113
rally/env/platform.py
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
# 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 rally.common.plugin import plugin
|
||||
from rally.common import utils
|
||||
from rally.common import validation
|
||||
|
||||
|
||||
class _PlatformStatus(utils.ImmutableMixin, utils.EnumMixin):
|
||||
"""Rally Env Statuses."""
|
||||
|
||||
INIT = "INITIALIZING"
|
||||
SKIPPED = "SKIPPED"
|
||||
|
||||
READY = "READY"
|
||||
FAILED_TO_CREATE = "FAILED TO CREATE"
|
||||
|
||||
DESTROYING = "DESTROYING"
|
||||
FAILED_TO_DESTROY = "FAILED TO DESTROY"
|
||||
DESTROYED = "DESTROYED"
|
||||
|
||||
TRANSITION_TABLE = {
|
||||
INIT: (READY, SKIPPED, FAILED_TO_CREATE),
|
||||
READY: (DESTROYING, ),
|
||||
FAILED_TO_CREATE: (DESTROYING, ),
|
||||
DESTROYING: (DESTROYED, FAILED_TO_DESTROY),
|
||||
FAILED_TO_DESTROY: (DESTROYING, )
|
||||
}
|
||||
|
||||
|
||||
STATUS = _PlatformStatus()
|
||||
|
||||
|
||||
def configure(name, platform):
|
||||
"""Configure platform plugin.
|
||||
|
||||
Platform is building block for Env.
|
||||
|
||||
:param name: str platform plugin name
|
||||
:param platform: str thing that is described by this plugin
|
||||
|
||||
"""
|
||||
def wrapper(cls):
|
||||
return plugin.configure(name=name, platform=platform)(cls)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@validation.add_default("jsonschema")
|
||||
@plugin.base()
|
||||
class Platform(plugin.Plugin, validation.ValidatablePluginMixin):
|
||||
|
||||
def __init__(self, spec,
|
||||
uuid=None, plugin_data=None, platform_data=None, status=None):
|
||||
"""Create instance of platform and validates config.
|
||||
|
||||
:param platform_config: Platform configuration file
|
||||
:param platform_data: Platform specific data returned by create method
|
||||
"""
|
||||
self.spec = spec
|
||||
self.uuid = uuid
|
||||
self.plugin_data = plugin_data
|
||||
self.platform_data = platform_data
|
||||
self.status = status
|
||||
|
||||
def create(self):
|
||||
"""Perform operations required to create platform.
|
||||
|
||||
:returns: Complete platform data as dictionary
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"Platform %s doesn't support create action" % self.get_fullname())
|
||||
|
||||
def destroy(self):
|
||||
"""Destroys platform."""
|
||||
raise NotImplementedError(
|
||||
"Platform %s doesn't support destroy action" % self.get_fullname())
|
||||
|
||||
def update(self, new_spec):
|
||||
"""Updates existing platform config and returns new platform data.
|
||||
|
||||
:param new_platform_config: New platform config.
|
||||
:returns: Complete platform data as dictionary
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"Platform %s doesn't support update action" % self.get_fullname())
|
||||
|
||||
def cleanup(self, task_uuid=None):
|
||||
"""Disaster cleanup for platform."""
|
||||
raise NotImplementedError(
|
||||
"Platform %s doesn't support cleanup action" % self.get_fullname())
|
||||
|
||||
def check_health(self):
|
||||
"""Check whatever platform is alive."""
|
||||
raise NotImplementedError(
|
||||
"Platform %s doesn't support health check action"
|
||||
% self.get_fullname())
|
||||
|
||||
def info(self):
|
||||
"""Return information about platform as dictionary."""
|
||||
raise NotImplementedError(
|
||||
"Platform %s doesn't support info action" % self.get_fullname())
|
@ -85,6 +85,27 @@ class DBRecordExists(DBException):
|
||||
msg_fmt = "Record with %(field)s = %(value)s already exists in %(table)s"
|
||||
|
||||
|
||||
class ManagerException(RallyException):
|
||||
error_code = 500
|
||||
msg_fmt = "Internal error: %(message)s"
|
||||
|
||||
|
||||
class ManagerInvalidSpec(ManagerException):
|
||||
error_code = 409
|
||||
msg_fmt = "%(mgr)s manager got invalid spec: \n%(errors)s"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs["errors"] = "\n".join(kwargs["errors"])
|
||||
self.spec = kwargs.pop("spec", "")
|
||||
super(ManagerInvalidSpec, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class ManagerInvalidState(ManagerException):
|
||||
error_code = 500
|
||||
msg_fmt = ("%(mgr)s manager in invalid state "
|
||||
"expected `%(expected)s' actual `%(actual)s' ")
|
||||
|
||||
|
||||
class InvalidArgumentsException(RallyException):
|
||||
error_code = 455
|
||||
msg_fmt = "Invalid arguments: '%(message)s'"
|
||||
|
@ -606,6 +606,220 @@ class WorkloadDataTestCase(test.DBTestCase):
|
||||
self.assertEqual(self.workload_uuid, workload_data["workload_uuid"])
|
||||
|
||||
|
||||
class EnvTestCase(test.DBTestCase):
|
||||
|
||||
def test_env_get(self):
|
||||
env1 = db.env_create("name", "STATUS42", "descr", {}, {}, [])
|
||||
env2 = db.env_create("name2", "STATUS42", "descr", {}, {}, [])
|
||||
|
||||
self.assertEqual(env1, db.env_get(env1["uuid"]))
|
||||
self.assertEqual(env1, db.env_get(env1["name"]))
|
||||
self.assertEqual(env2, db.env_get(env2["uuid"]))
|
||||
self.assertEqual(env2, db.env_get(env2["name"]))
|
||||
|
||||
def test_env_get_not_found(self):
|
||||
self.assertRaises(exceptions.DBRecordNotFound,
|
||||
db.env_get, "non-existing-env")
|
||||
|
||||
def test_env_get_status(self):
|
||||
env = db.env_create("name", "STATUS42", "descr", {}, {}, [])
|
||||
self.assertEqual("STATUS42", db.env_get_status(env["uuid"]))
|
||||
|
||||
def test_env_get_status_non_existing(self):
|
||||
self.assertRaises(exceptions.DBRecordNotFound,
|
||||
db.env_get_status, "non-existing-env")
|
||||
|
||||
def test_env_list(self):
|
||||
for i in range(3):
|
||||
db.env_create("name %s" % i, "STATUS42", "descr", {}, {}, [])
|
||||
self.assertEqual(i + 1, len(db.env_list()))
|
||||
|
||||
all_ = db.env_list()
|
||||
self.assertIsInstance(all_, list)
|
||||
for env in all_:
|
||||
self.assertIsInstance(env, dict)
|
||||
|
||||
self.assertEqual(set("name %s" % i for i in range(3)),
|
||||
set(e["name"] for e in db.env_list()))
|
||||
|
||||
def test_env_list_filter_by_status(self):
|
||||
db.env_create("name 1", "STATUS42", "descr", {}, {}, [])
|
||||
db.env_create("name 2", "STATUS42", "descr", {}, {}, [])
|
||||
db.env_create("name 3", "STATUS43", "descr", {}, {}, [])
|
||||
|
||||
result = db.env_list("STATUS42")
|
||||
self.assertEqual(2, len(result))
|
||||
self.assertEqual(set(["name 1", "name 2"]),
|
||||
set(r["name"] for r in result))
|
||||
result = db.env_list("STATUS43")
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual("name 3", result[0]["name"])
|
||||
|
||||
def test_env_create(self):
|
||||
env = db.env_create(
|
||||
"name", "status", "descr", {"extra": "test"}, {"spec": "spec"}, [])
|
||||
|
||||
self.assertIsInstance(env, dict)
|
||||
self.assertIsNotNone(env["uuid"])
|
||||
self.assertEqual(env, db.env_get(env["uuid"]))
|
||||
self.assertEqual("name", env["name"])
|
||||
self.assertEqual("status", env["status"])
|
||||
self.assertEqual("descr", env["description"])
|
||||
self.assertEqual({"extra": "test"}, env["extras"])
|
||||
self.assertEqual({"spec": "spec"}, env["spec"])
|
||||
|
||||
def test_env_create_duplicate_env(self):
|
||||
db.env_create("name", "status", "descr", {}, {}, [])
|
||||
self.assertRaises(exceptions.DBRecordExists,
|
||||
db.env_create, "name", "status", "descr", {}, {}, [])
|
||||
|
||||
def teet_env_create_with_platforms(self):
|
||||
platforms = [
|
||||
{
|
||||
"status": "ANY",
|
||||
"plugin_name": "plugin_%s@plugin" % i,
|
||||
"plugin_spec": {},
|
||||
"platform_name": "plugin"
|
||||
}
|
||||
for i in range(3)
|
||||
]
|
||||
env = db.env_create("name", "status", "descr", {}, {}, platforms)
|
||||
db_platforms = db.platforms_list(env["uuid"])
|
||||
self.assertEqual(3, len(db_platforms))
|
||||
|
||||
def test_env_rename(self):
|
||||
env = db.env_create(
|
||||
"name", "status", "descr", {"extra": "test"}, {"spec": "spec"}, [])
|
||||
|
||||
self.assertTrue(db.env_rename(env["uuid"], env["name"], "name2"))
|
||||
self.assertEqual("name2", db.env_get(env["uuid"])["name"])
|
||||
|
||||
def test_env_rename_duplicate(self):
|
||||
env1 = db.env_create("name", "status", "descr", {}, {}, [])
|
||||
env2 = db.env_create("name2", "status", "descr", {}, {}, [])
|
||||
self.assertRaises(
|
||||
exceptions.DBRecordExists,
|
||||
db.env_rename, env1["uuid"], env1["name"], env2["name"])
|
||||
|
||||
def test_env_update(self):
|
||||
env = db.env_create("name", "status", "descr", {}, {}, [])
|
||||
self.assertTrue(db.env_update(env["uuid"]))
|
||||
self.assertTrue(
|
||||
db.env_update(env["uuid"], "another_descr", {"extra": 123}))
|
||||
|
||||
env = db.env_get(env["uuid"])
|
||||
self.assertEqual("another_descr", env["description"])
|
||||
self.assertEqual({"extra": 123}, env["extras"])
|
||||
|
||||
def test_evn_set_status(self):
|
||||
env = db.env_create("name", "status", "descr", {}, {}, [])
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.DBConflict,
|
||||
db.env_set_status, env["uuid"], "wrong_old_status", "new_status")
|
||||
env = db.env_get(env["uuid"])
|
||||
self.assertEqual("status", env["status"])
|
||||
|
||||
self.assertTrue(
|
||||
db.env_set_status(env["uuid"], "status", "new_status"))
|
||||
|
||||
env = db.env_get(env["uuid"])
|
||||
self.assertEqual("new_status", env["status"])
|
||||
|
||||
def test_env_delete_cascade(self):
|
||||
platforms = [
|
||||
{
|
||||
"status": "ANY",
|
||||
"plugin_name": "plugin_%s@plugin" % i,
|
||||
"plugin_spec": {},
|
||||
"platform_name": "plugin"
|
||||
}
|
||||
for i in range(3)
|
||||
]
|
||||
env = db.env_create("name", "status", "descr", {}, {}, platforms)
|
||||
db.env_delete_cascade(env["uuid"])
|
||||
|
||||
self.assertEqual(0, len(db.env_list()))
|
||||
self.assertEqual(0, len(db.platforms_list(env["uuid"])))
|
||||
|
||||
|
||||
class PlatformTestCase(test.DBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PlatformTestCase, self).setUp()
|
||||
platforms = [
|
||||
{
|
||||
"status": "ANY",
|
||||
"plugin_name": "plugin_%s@plugin" % i,
|
||||
"plugin_spec": {},
|
||||
"platform_name": "plugin"
|
||||
}
|
||||
for i in range(5)
|
||||
]
|
||||
self.env1 = db.env_create("env1", "init", "", {}, {}, platforms[:2])
|
||||
self.env2 = db.env_create("env2", "init", "", {}, {}, platforms[2:])
|
||||
|
||||
def test_platform_get(self):
|
||||
for p in db.platforms_list(self.env1["uuid"]):
|
||||
self.assertEqual(p, db.platform_get(p["uuid"]))
|
||||
|
||||
def test_platform_get_not_found(self):
|
||||
self.assertRaises(exceptions.DBRecordNotFound,
|
||||
db.platform_get, "non_existing")
|
||||
|
||||
def test_platforms_list(self):
|
||||
self.assertEqual(0, len(db.platforms_list("non_existing")))
|
||||
self.assertEqual(2, len(db.platforms_list(self.env1["uuid"])))
|
||||
self.assertEqual(3, len(db.platforms_list(self.env2["uuid"])))
|
||||
|
||||
def test_platform_set_status(self):
|
||||
platforms = db.platforms_list(self.env1["uuid"])
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.DBConflict,
|
||||
db.platform_set_status,
|
||||
platforms[0]["uuid"], "OTHER", "NEW_STATUS")
|
||||
self.assertEqual("ANY",
|
||||
db.platform_get(platforms[0]["uuid"])["status"])
|
||||
|
||||
self.assertTrue(db.platform_set_status(
|
||||
platforms[0]["uuid"], "ANY", "NEW_STATUS"))
|
||||
self.assertEqual("NEW_STATUS",
|
||||
db.platform_get(platforms[0]["uuid"])["status"])
|
||||
|
||||
self.assertEqual("ANY",
|
||||
db.platform_get(platforms[1]["uuid"])["status"])
|
||||
|
||||
def test_platform_set_data(self):
|
||||
platforms = db.platforms_list(self.env1["uuid"])
|
||||
uuid = platforms[0]["uuid"]
|
||||
|
||||
self.assertTrue(db.platform_set_data(uuid))
|
||||
self.assertTrue(
|
||||
db.platform_set_data(uuid, platform_data={"platform": "data"}))
|
||||
in_db = db.platform_get(uuid)
|
||||
self.assertEqual({"platform": "data"}, in_db["platform_data"])
|
||||
self.assertEqual({}, in_db["plugin_data"])
|
||||
|
||||
self.assertTrue(
|
||||
db.platform_set_data(uuid, plugin_data={"plugin": "data"}))
|
||||
in_db = db.platform_get(uuid)
|
||||
self.assertEqual({"platform": "data"}, in_db["platform_data"])
|
||||
self.assertEqual({"plugin": "data"}, in_db["plugin_data"])
|
||||
|
||||
self.assertTrue(
|
||||
db.platform_set_data(uuid, platform_data={"platform": "data2"}))
|
||||
in_db = db.platform_get(uuid)
|
||||
self.assertEqual({"platform": "data2"}, in_db["platform_data"])
|
||||
self.assertEqual({"plugin": "data"}, in_db["plugin_data"])
|
||||
|
||||
self.assertFalse(db.platform_set_data(
|
||||
"non_existing", platform_data={}))
|
||||
in_db = db.platform_get(uuid)
|
||||
# just check that nothing changed after wrong uuid passed
|
||||
self.assertEqual({"platform": "data2"}, in_db["platform_data"])
|
||||
|
||||
|
||||
class DeploymentTestCase(test.DBTestCase):
|
||||
def test_deployment_create(self):
|
||||
deploy = db.deployment_create({"config": {"opt": "val"}})
|
||||
|
@ -39,8 +39,6 @@ class FakeSerializable(object):
|
||||
|
||||
@ddt.ddt
|
||||
class SerializeTestCase(test.DBTestCase):
|
||||
def setUp(self):
|
||||
super(SerializeTestCase, self).setUp()
|
||||
|
||||
@ddt.data(
|
||||
{"data": 1, "serialized": 1},
|
||||
@ -80,3 +78,14 @@ class SerializeTestCase(test.DBTestCase):
|
||||
return Fake()
|
||||
|
||||
self.assertRaises(exceptions.DBException, fake_method)
|
||||
|
||||
|
||||
class ModelQueryTestCase(test.DBTestCase):
|
||||
|
||||
def test_model_query_wrong_model(self):
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
self.assertRaises(exceptions.DBException,
|
||||
db_api.Connection().model_query, Foo)
|
||||
|
0
tests/unit/env/__init__.py
vendored
Normal file
0
tests/unit/env/__init__.py
vendored
Normal file
652
tests/unit/env/test_env_mgr.py
vendored
Normal file
652
tests/unit/env/test_env_mgr.py
vendored
Normal file
@ -0,0 +1,652 @@
|
||||
# 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 rally.env import env_mgr
|
||||
from rally.env import platform
|
||||
from rally import exceptions
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
class EnvManagerTestCase(test.TestCase):
|
||||
|
||||
def test_init(self):
|
||||
data = {"uuid": "any", "balbalba": "balbal"}
|
||||
mgr = env_mgr.EnvManager(data)
|
||||
self.assertEqual("any", mgr.uuid)
|
||||
self.assertEqual(data, mgr._env)
|
||||
|
||||
@mock.patch("rally.common.db.env_get_status")
|
||||
def test_status_property(self, mock_env_get_status):
|
||||
self.assertEqual(mock_env_get_status.return_value,
|
||||
env_mgr.EnvManager({"uuid": "any"}).status)
|
||||
|
||||
mock_env_get_status.assert_called_once_with("any")
|
||||
|
||||
@mock.patch("rally.common.db.platforms_list")
|
||||
@mock.patch("rally.common.db.env_get")
|
||||
def test_data_property(self, mock_env_get, mock_platforms_list):
|
||||
mock_platforms_list.return_value = [42, 43, 44]
|
||||
mock_env_get.return_value = {
|
||||
"id": "66",
|
||||
"uuid": "666",
|
||||
"created_at": mock.Mock(),
|
||||
"updated_at": mock.Mock(),
|
||||
"name": "42",
|
||||
"description": "Some description",
|
||||
"status": "some status",
|
||||
"spec": "some_spec",
|
||||
"extras": "some_extras",
|
||||
}
|
||||
|
||||
result = env_mgr.EnvManager({"uuid": 111}).data
|
||||
for field in ["name", "description", "status", "spec", "extras",
|
||||
"created_at", "updated_at", "uuid", "id"]:
|
||||
self.assertEqual(mock_env_get.return_value[field], result[field])
|
||||
self.assertEqual(mock_platforms_list.return_value, result["platforms"])
|
||||
|
||||
mock_platforms_list.assert_called_once_with(111)
|
||||
mock_env_get.assert_called_once_with(111)
|
||||
|
||||
@mock.patch("rally.common.db.platforms_list")
|
||||
def test__get_platforms(self, mock_platforms_list):
|
||||
|
||||
@platform.configure(name="some", platform="foo")
|
||||
class FooPlatform(platform.Platform):
|
||||
pass
|
||||
|
||||
mock_platforms_list.side_effect = [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"uuid": "1",
|
||||
"plugin_name": "some@foo",
|
||||
"plugin_data": "plugin_data",
|
||||
"plugin_spec": "plugin_data",
|
||||
"platform_data": "platform_data",
|
||||
"status": "INIT"
|
||||
},
|
||||
{
|
||||
"uuid": "2",
|
||||
"plugin_name": "some@foo",
|
||||
"plugin_data": None,
|
||||
"plugin_spec": "plugin_data",
|
||||
"platform_data": None,
|
||||
"status": "CREATED"
|
||||
},
|
||||
]
|
||||
]
|
||||
|
||||
self.assertEqual([], env_mgr.EnvManager({"uuid": 42})._get_platforms())
|
||||
mock_platforms_list.assert_called_once_with(42)
|
||||
|
||||
result = env_mgr.EnvManager({"uuid": 43})._get_platforms()
|
||||
self.assertEqual(2, len(result))
|
||||
|
||||
for i, r in enumerate(sorted(result, key=lambda x: x.uuid)):
|
||||
self.assertIsInstance(r, FooPlatform)
|
||||
self.assertEqual("some@foo", r.get_fullname())
|
||||
self.assertEqual(str(i + 1), r.uuid)
|
||||
|
||||
mock_platforms_list.assert_has_calls([mock.call(42), mock.call(43)])
|
||||
|
||||
@mock.patch("rally.common.db.env_get",
|
||||
return_value={"uuid": "1"})
|
||||
def test_get(self, mock_env_get):
|
||||
self.assertEqual("1", env_mgr.EnvManager.get("1").uuid)
|
||||
mock_env_get.assert_called_once_with("1")
|
||||
|
||||
@mock.patch("rally.common.db.env_list",
|
||||
return_value=[{"uuid": "1"}, {"uuid": "2"}])
|
||||
def test_list(self, mock_env_list):
|
||||
result = env_mgr.EnvManager.list()
|
||||
|
||||
for r in result:
|
||||
self.assertIsInstance(r, env_mgr.EnvManager)
|
||||
|
||||
self.assertEqual(set(r.uuid for r in result), set(["1", "2"]))
|
||||
self.assertEqual(2, len(result))
|
||||
mock_env_list.assert_called_once_with(status=None)
|
||||
|
||||
@mock.patch("rally.common.db.env_create")
|
||||
def test__validate_and_create_env_empty_spec(self, mock_env_create):
|
||||
mock_env_create.return_value = {"uuid": "1"}
|
||||
env = env_mgr.EnvManager._validate_and_create_env(
|
||||
"name", "descr", {"extras": ""}, {})
|
||||
|
||||
self.assertIsInstance(env, env_mgr.EnvManager)
|
||||
self.assertEqual("1", env.uuid)
|
||||
mock_env_create.assert_called_once_with(
|
||||
"name", env_mgr.STATUS.INIT, "descr", {"extras": ""}, {}, [])
|
||||
|
||||
@mock.patch("rally.common.db.env_create")
|
||||
def test__validate_and_create_env_with_spec(self, mock_env_create):
|
||||
mock_env_create.return_value = {"uuid": "1"}
|
||||
|
||||
@platform.configure("existing", platform="valid1")
|
||||
class Platform1(platform.Platform):
|
||||
CONFIG_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {"a": {"type": "string"}},
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
@platform.configure("2", platform="valid2")
|
||||
class Platform2(platform.Platform):
|
||||
CONFIG_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {"b": {"type": "string"}},
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
self.addCleanup(Platform1.unregister)
|
||||
self.addCleanup(Platform2.unregister)
|
||||
|
||||
env_mgr.EnvManager._validate_and_create_env(
|
||||
"n", "d", "ext", {"valid1": {"a": "str"}})
|
||||
|
||||
expected_platforms = [{
|
||||
"status": platform.STATUS.INIT,
|
||||
"plugin_name": "existing@valid1",
|
||||
"plugin_spec": {"a": "str"},
|
||||
"platform_name": "valid1"
|
||||
}]
|
||||
|
||||
mock_env_create.assert_called_once_with(
|
||||
"n", env_mgr.STATUS.INIT, "d", "ext",
|
||||
{"existing@valid1": {"a": "str"}}, expected_platforms)
|
||||
|
||||
mock_env_create.reset_mock()
|
||||
self.assertRaises(
|
||||
exceptions.ManagerInvalidSpec,
|
||||
env_mgr.EnvManager._validate_and_create_env,
|
||||
"n", "d", "ext", {"valid1": {"a": "str"}, "2@valid2": {"c": 1}}
|
||||
)
|
||||
self.assertFalse(mock_env_create.called)
|
||||
|
||||
mock_env_create.reset_mock()
|
||||
self.assertRaises(
|
||||
exceptions.RallyException,
|
||||
env_mgr.EnvManager._validate_and_create_env,
|
||||
"n", "d", "ext", {"non_existing@nope": {"a": "str"}}
|
||||
)
|
||||
self.assertFalse(mock_env_create.called)
|
||||
|
||||
@mock.patch("rally.common.db.env_set_status")
|
||||
@mock.patch("rally.common.db.platform_set_data")
|
||||
@mock.patch("rally.common.db.platform_set_status")
|
||||
@mock.patch("rally.common.db.platforms_list")
|
||||
def test__create_platforms(self,
|
||||
mock_platforms_list, mock_platform_set_status,
|
||||
mock_platform_set_data, mock_env_set_status):
|
||||
|
||||
# One platform that passes successfully
|
||||
@platform.configure("passes", platform="create")
|
||||
class ValidPlatform(platform.Platform):
|
||||
def create(self):
|
||||
return {"platform": "data"}, {"plugin": "data"}
|
||||
|
||||
self.addCleanup(ValidPlatform.unregister)
|
||||
mock_platforms_list.return_value = [
|
||||
{
|
||||
"uuid": "p_uuid",
|
||||
"plugin_name": "passes@create",
|
||||
"plugin_spec": {},
|
||||
"plugin_data": None,
|
||||
"platform_data": None,
|
||||
"status": platform.STATUS.INIT
|
||||
}
|
||||
]
|
||||
env_mgr.EnvManager({"uuid": 121})._create_platforms()
|
||||
|
||||
mock_platforms_list.assert_called_once_with(121)
|
||||
mock_platform_set_status.assert_called_once_with(
|
||||
"p_uuid", platform.STATUS.INIT, platform.STATUS.READY)
|
||||
mock_platform_set_data.assert_called_once_with(
|
||||
"p_uuid",
|
||||
platform_data={"platform": "data"}, plugin_data={"plugin": "data"})
|
||||
mock_env_set_status.assert_called_once_with(
|
||||
121, env_mgr.STATUS.INIT, env_mgr.STATUS.READY)
|
||||
|
||||
@mock.patch("rally.common.db.env_set_status")
|
||||
@mock.patch("rally.common.db.platform_set_status")
|
||||
@mock.patch("rally.env.env_mgr.EnvManager._get_platforms")
|
||||
def test__create_platforms_failed(self, mock__get_platforms,
|
||||
mock_platform_set_status,
|
||||
mock_env_set_status):
|
||||
# Check when first fails, second is marked as skipped
|
||||
|
||||
@platform.configure("bad", platform="create")
|
||||
class InValidPlatform(platform.Platform):
|
||||
def create(self):
|
||||
raise Exception("I don't want to work!")
|
||||
|
||||
@platform.configure("good_but_skipped", platform="create")
|
||||
class ValidPlatform(platform.Platform):
|
||||
def create(self):
|
||||
return {"platform": "data"}, {"plugin": "data"}
|
||||
|
||||
for p in [InValidPlatform, ValidPlatform]:
|
||||
self.addCleanup(p.unregister)
|
||||
|
||||
mock__get_platforms.return_value = [
|
||||
InValidPlatform({}, uuid=1), ValidPlatform({}, uuid=2)]
|
||||
|
||||
env_mgr.EnvManager({"uuid": 42})._create_platforms()
|
||||
|
||||
mock_env_set_status.assert_called_once_with(
|
||||
42, env_mgr.STATUS.INIT, env_mgr.STATUS.FAILED_TO_CREATE)
|
||||
|
||||
mock_platform_set_status.assert_has_calls([
|
||||
mock.call(1, platform.STATUS.INIT,
|
||||
platform.STATUS.FAILED_TO_CREATE),
|
||||
mock.call(2, platform.STATUS.INIT, platform.STATUS.SKIPPED),
|
||||
])
|
||||
|
||||
@mock.patch("rally.common.db.env_set_status")
|
||||
@mock.patch("rally.common.db.platform_set_status")
|
||||
@mock.patch("rally.common.db.platform_set_data")
|
||||
@mock.patch("rally.env.env_mgr.EnvManager._get_platforms")
|
||||
def test__create_platforms_when_db_issues_autodestroy(
|
||||
self, mock__get_platforms, mock_platform_set_data,
|
||||
mock_platform_set_status, mock_env_set_status):
|
||||
# inject db errors check that auto destroy is called
|
||||
platform1 = mock.MagicMock()
|
||||
platform1.uuid = 11
|
||||
platform1.destroy.side_effect = Exception
|
||||
platform1.create.return_value = ("platform_d", "plugin_d")
|
||||
platform2 = mock.MagicMock()
|
||||
platform2.uuid = 22
|
||||
mock__get_platforms.return_value = [platform1, platform2]
|
||||
mock_platform_set_data.side_effect = Exception
|
||||
|
||||
env_mgr.EnvManager({"uuid": 42})._create_platforms()
|
||||
|
||||
mock_platform_set_status.assert_called_once_with(
|
||||
22, platform.STATUS.INIT, platform.STATUS.SKIPPED)
|
||||
mock_platform_set_data.assert_called_once_with(
|
||||
11, platform_data="platform_d", plugin_data="plugin_d")
|
||||
mock_env_set_status.assert_called_once_with(
|
||||
42, env_mgr.STATUS.INIT, env_mgr.STATUS.FAILED_TO_CREATE)
|
||||
|
||||
@mock.patch("rally.common.db.platforms_list", return_value=[])
|
||||
@mock.patch("rally.common.db.env_set_status")
|
||||
@mock.patch("rally.common.db.env_create", return_value={"uuid": 121})
|
||||
def test_create(self, mock_env_create, mock_env_set_status,
|
||||
mock_platforms_list):
|
||||
# NOTE(boris-42): Just check with empty spec that just check workflow
|
||||
result = env_mgr.EnvManager.create("a", "descr", "extras", {})
|
||||
|
||||
self.assertIsInstance(result, env_mgr.EnvManager)
|
||||
self.assertEqual(121, result.uuid)
|
||||
|
||||
@mock.patch("rally.common.db.env_rename")
|
||||
def test_rename(self, mock_env_rename):
|
||||
env = env_mgr.EnvManager({"uuid": "11", "name": "n"})
|
||||
|
||||
self.assertTrue(env.rename, env.rename("n"))
|
||||
self.assertEqual(0, mock_env_rename.call_count)
|
||||
self.assertTrue(env.rename, env.rename("n2"))
|
||||
mock_env_rename.assert_called_once_with("11", "n", "n2")
|
||||
|
||||
@mock.patch("rally.common.db.env_update")
|
||||
def test_update(self, mock_env_update):
|
||||
env = env_mgr.EnvManager(
|
||||
{"uuid": "11", "description": "d", "extras": "e"})
|
||||
|
||||
self.assertTrue(env.update())
|
||||
self.assertEqual(0, mock_env_update.call_count)
|
||||
|
||||
env.update(description="d2", extras="e2")
|
||||
mock_env_update.assert_called_once_with(
|
||||
"11", description="d2", extras="e2")
|
||||
|
||||
def test_update_spec(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
env_mgr.EnvManager({"uuid": 1}).update_spec, "")
|
||||
|
||||
@mock.patch("rally.env.env_mgr.EnvManager._get_platforms")
|
||||
def test_check_health(self, mock__get_platforms):
|
||||
|
||||
valid_result = {
|
||||
"available": False,
|
||||
"message": "Nope I don't want to work"
|
||||
}
|
||||
|
||||
@platform.configure(name="valid", platform="check")
|
||||
class ValidPlatform(platform.Platform):
|
||||
|
||||
def check_health(self):
|
||||
return valid_result
|
||||
|
||||
@platform.configure(name="broken_fromat", platform="check")
|
||||
class BrokenFormatPlatform(platform.Platform):
|
||||
|
||||
def check_health(self):
|
||||
return {"something": "is wrong here in format"}
|
||||
|
||||
@platform.configure(name="just_broken", platform="check")
|
||||
class JustBrokenPlatform(platform.Platform):
|
||||
|
||||
def check_health(self):
|
||||
raise Exception("This is really bad exception")
|
||||
|
||||
for p in [ValidPlatform, BrokenFormatPlatform, JustBrokenPlatform]:
|
||||
self.addCleanup(p.unregister)
|
||||
|
||||
mock__get_platforms.side_effect = [
|
||||
[ValidPlatform("spec1")],
|
||||
[ValidPlatform("spec1"), BrokenFormatPlatform("spec2")],
|
||||
[JustBrokenPlatform("spec3")]
|
||||
]
|
||||
|
||||
self.assertEqual({"valid@check": valid_result},
|
||||
env_mgr.EnvManager({"uuid": "42"}).check_health())
|
||||
|
||||
broken_msg = "Plugin %s.check_health() method is broken"
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
"valid@check": valid_result,
|
||||
"broken_fromat@check": {
|
||||
"message": broken_msg % "broken_fromat@check",
|
||||
"available": False
|
||||
}
|
||||
},
|
||||
env_mgr.EnvManager({"uuid": "43"}).check_health())
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
"just_broken@check": {
|
||||
"message": broken_msg % "just_broken@check",
|
||||
"available": False,
|
||||
"traceback": (Exception, mock.ANY, mock.ANY)
|
||||
}
|
||||
},
|
||||
env_mgr.EnvManager({"uuid": "44"}).check_health())
|
||||
|
||||
mock__get_platforms.assert_has_calls([mock.call()] * 3)
|
||||
|
||||
@mock.patch("rally.env.env_mgr.EnvManager._get_platforms")
|
||||
def test_get_info(self, mock__get_platforms):
|
||||
|
||||
@platform.configure(name="valid", platform="info")
|
||||
class InfoValid(platform.Platform):
|
||||
|
||||
def info(self):
|
||||
return {"info": "it works!", "error": ""}
|
||||
|
||||
@platform.configure(name="wrong_fmt", platform="info")
|
||||
class InfoWrongFormat(platform.Platform):
|
||||
|
||||
def info(self):
|
||||
return {"something": "is wrong"}
|
||||
|
||||
@platform.configure(name="broken", platform="info")
|
||||
class InfoBroken(platform.Platform):
|
||||
|
||||
def info(self):
|
||||
raise Exception("This should not happen")
|
||||
|
||||
for p in [InfoValid, InfoWrongFormat, InfoBroken]:
|
||||
self.addCleanup(p.unregister)
|
||||
|
||||
mock__get_platforms.side_effect = [
|
||||
[InfoValid("spec1")],
|
||||
[InfoValid("spec1"), InfoWrongFormat("spec2")],
|
||||
[InfoValid("spec1"), InfoBroken("spec3")],
|
||||
]
|
||||
|
||||
valid_result = {"info": "it works!", "error": ""}
|
||||
|
||||
self.assertEqual({"valid@info": valid_result},
|
||||
env_mgr.EnvManager({"uuid": "42"}).get_info())
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
"valid@info": valid_result,
|
||||
"wrong_fmt@info": {
|
||||
"error": "Plugin wrong_fmt@info.info() method is broken",
|
||||
"info": None
|
||||
}
|
||||
},
|
||||
env_mgr.EnvManager({"uuid": "43"}).get_info())
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
"valid@info": valid_result,
|
||||
"broken@info": {
|
||||
"error": "Plugin broken@info.info() method is broken",
|
||||
"info": None,
|
||||
"traceback": (Exception, mock.ANY, mock.ANY)
|
||||
}
|
||||
},
|
||||
env_mgr.EnvManager({"uuid": "44"}).get_info())
|
||||
|
||||
mock__get_platforms.assert_has_calls([mock.call()] * 3)
|
||||
|
||||
@mock.patch("rally.common.db.env_set_status")
|
||||
@mock.patch("rally.env.env_mgr.EnvManager._get_platforms")
|
||||
def test_cleanup(self, mock__get_platforms, mock_env_set_status):
|
||||
|
||||
valid_result = {
|
||||
"discovered": 10,
|
||||
"deleted": 6,
|
||||
"failed": 4,
|
||||
"resources": {
|
||||
"vm": {
|
||||
"discovered": 2,
|
||||
"failed": 2,
|
||||
"deleted": 0
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"resource_id": "1",
|
||||
"resource_type": "vm",
|
||||
"message": "something"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@platform.configure(name="valid", platform="clean")
|
||||
class CleanValid(platform.Platform):
|
||||
|
||||
def cleanup(self, task_uuid=None):
|
||||
return valid_result
|
||||
|
||||
@platform.configure(name="wrong", platform="clean")
|
||||
class CleanWrongFormat(platform.Platform):
|
||||
|
||||
def cleanup(self, task_uuid):
|
||||
return {"something": "is wrong"}
|
||||
|
||||
@platform.configure(name="broken", platform="clean")
|
||||
class CleanBroken(platform.Platform):
|
||||
|
||||
def cleanup(self, task_uuid):
|
||||
raise Exception("This should not happen")
|
||||
|
||||
for p in [CleanValid, CleanWrongFormat, CleanBroken]:
|
||||
self.addCleanup(p.unregister)
|
||||
|
||||
mock__get_platforms.return_value = [
|
||||
CleanValid("spec1"),
|
||||
CleanBroken("spec2"),
|
||||
CleanWrongFormat("spec3")
|
||||
]
|
||||
|
||||
result = env_mgr.EnvManager({"uuid": 424}).cleanup()
|
||||
mock__get_platforms.assert_called_once_with()
|
||||
mock_env_set_status.assert_has_calls([
|
||||
mock.call(424, env_mgr.STATUS.READY, env_mgr.STATUS.CLEANING),
|
||||
mock.call(424, env_mgr.STATUS.CLEANING, env_mgr.STATUS.READY)
|
||||
])
|
||||
self.assertIsInstance(result, dict)
|
||||
self.assertEqual(3, len(result))
|
||||
self.assertEqual(valid_result, result["valid@clean"])
|
||||
self.assertEqual(
|
||||
{
|
||||
"discovered": 0, "deleted": 0, "failed": 0, "resources": {},
|
||||
"errors": [{
|
||||
"message": "Plugin wrong@clean.cleanup() method is broken",
|
||||
}]
|
||||
},
|
||||
result["wrong@clean"]
|
||||
)
|
||||
self.assertEqual(
|
||||
{
|
||||
"discovered": 0, "deleted": 0, "failed": 0, "resources": {},
|
||||
"errors": [{
|
||||
"message": "Plugin broken@clean.cleanup() method is "
|
||||
"broken",
|
||||
"traceback": (Exception, mock.ANY, mock.ANY)
|
||||
}]
|
||||
},
|
||||
result["broken@clean"]
|
||||
)
|
||||
|
||||
@mock.patch("rally.env.env_mgr.EnvManager.cleanup",
|
||||
return_value={"errors": [121]})
|
||||
def test_destory_cleanup_failed(self, mock_env_manager_cleanup):
|
||||
self.assertEqual(
|
||||
{
|
||||
"cleanup_info": {
|
||||
"errors": [121],
|
||||
"skipped": False
|
||||
},
|
||||
"destroy_info": {
|
||||
"skipped": True,
|
||||
"platforms": {},
|
||||
"message": "Skipped because cleanup failed"
|
||||
}
|
||||
},
|
||||
env_mgr.EnvManager({"uuid": 42}).destroy()
|
||||
)
|
||||
mock_env_manager_cleanup.assert_called_once_with()
|
||||
|
||||
@mock.patch("rally.env.env_mgr.EnvManager._get_platforms", return_value=[])
|
||||
@mock.patch("rally.common.db.env_set_status")
|
||||
def test_destroy_no_platforms(self,
|
||||
mock_env_set_status, mock__get_platforms):
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
"cleanup_info": {"skipped": True},
|
||||
"destroy_info": {"skipped": False, "platforms": {}}
|
||||
},
|
||||
env_mgr.EnvManager({"uuid": 42}).destroy(skip_cleanup=True)
|
||||
)
|
||||
mock_env_set_status.assert_has_calls([
|
||||
mock.call(42, env_mgr.STATUS.READY, env_mgr.STATUS.DESTROYING),
|
||||
mock.call(42, env_mgr.STATUS.DESTROYING, env_mgr.STATUS.DESTROYED)
|
||||
])
|
||||
mock__get_platforms.assert_called_once_with()
|
||||
|
||||
@mock.patch("rally.common.db.env_set_status")
|
||||
@mock.patch("rally.common.db.platform_set_status")
|
||||
@mock.patch("rally.env.env_mgr.EnvManager._get_platforms")
|
||||
def test_destory_with_platforms(self, mock__get_platforms,
|
||||
mock_platform_set_status,
|
||||
mock_env_set_status):
|
||||
platform1 = mock.MagicMock()
|
||||
platform1.get_fullname.return_value = "p_destroyed"
|
||||
platform1.status = platform.STATUS.DESTROYED
|
||||
|
||||
platform2 = mock.MagicMock()
|
||||
platform2.get_fullname.return_value = "p_valid"
|
||||
platform2.status = platform.STATUS.READY
|
||||
|
||||
platform3 = mock.MagicMock()
|
||||
platform3.get_fullname.return_value = "p_invalid"
|
||||
platform3.destroy.side_effect = Exception
|
||||
platform3.status = platform.STATUS.READY
|
||||
|
||||
mock__get_platforms.return_value = [
|
||||
platform1, platform2, platform3
|
||||
]
|
||||
|
||||
result = env_mgr.EnvManager({"uuid": 666}).destroy(skip_cleanup=True)
|
||||
self.assertIsInstance(result, dict)
|
||||
self.assertEqual(2, len(result))
|
||||
self.assertEqual({"skipped": True}, result["cleanup_info"])
|
||||
self.assertEqual(
|
||||
{
|
||||
"skipped": False,
|
||||
"platforms": {
|
||||
"p_destroyed": {
|
||||
"message": "Platform is already destroyed. Do nothing",
|
||||
"status": {
|
||||
"old": platform.STATUS.DESTROYED,
|
||||
"new": platform.STATUS.DESTROYED
|
||||
},
|
||||
},
|
||||
"p_valid": {
|
||||
"message": "Successfully destroyed",
|
||||
"status": {
|
||||
"old": platform.STATUS.READY,
|
||||
"new": platform.STATUS.DESTROYED
|
||||
},
|
||||
},
|
||||
"p_invalid": {
|
||||
"message": "Failed to destroy",
|
||||
"status": {
|
||||
"old": platform.STATUS.READY,
|
||||
"new": platform.STATUS.FAILED_TO_DESTROY
|
||||
},
|
||||
"traceback": (Exception, mock.ANY, mock.ANY)
|
||||
}
|
||||
}
|
||||
},
|
||||
result["destroy_info"])
|
||||
|
||||
mock__get_platforms.assert_called_once_with()
|
||||
mock_platform_set_status.assert_has_calls([
|
||||
mock.call(platform2.uuid,
|
||||
platform.STATUS.READY,
|
||||
platform.STATUS.DESTROYING),
|
||||
mock.call(platform2.uuid,
|
||||
platform.STATUS.DESTROYING,
|
||||
platform.STATUS.DESTROYED),
|
||||
mock.call(platform3.uuid,
|
||||
platform.STATUS.READY,
|
||||
platform.STATUS.DESTROYING),
|
||||
mock.call(platform3.uuid,
|
||||
platform.STATUS.DESTROYING,
|
||||
platform.STATUS.FAILED_TO_DESTROY)
|
||||
])
|
||||
|
||||
@mock.patch("rally.common.db.env_get_status")
|
||||
@mock.patch("rally.common.db.env_delete_cascade")
|
||||
def test_delete(self, mock_env_delete_cascade, mock_env_get_status):
|
||||
mock_env_get_status.side_effect = [
|
||||
"WRONG", env_mgr.STATUS.DESTROYED
|
||||
]
|
||||
self.assertRaises(exceptions.ManagerInvalidState,
|
||||
env_mgr.EnvManager({"uuid": "42"}).delete)
|
||||
self.assertFalse(mock_env_delete_cascade.called)
|
||||
|
||||
env_mgr.EnvManager({"uuid": "43"}).delete()
|
||||
mock_env_delete_cascade.assert_called_once_with("43")
|
||||
|
||||
mock_env_get_status.assert_has_calls(
|
||||
[mock.call("42"), mock.call("43")])
|
||||
|
||||
@mock.patch("rally.common.db.env_get_status")
|
||||
@mock.patch("rally.common.db.env_delete_cascade")
|
||||
def test_delete_force(self, mock_env_delete_cascade, mock_env_get_status):
|
||||
mock_env_get_status.return_value = "WRONG"
|
||||
env_mgr.EnvManager({"uuid": "44"}).delete(force=True)
|
||||
mock_env_delete_cascade.assert_called_once_with("44")
|
44
tests/unit/env/test_platform.py
vendored
Normal file
44
tests/unit/env/test_platform.py
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
# 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 rally.env import platform
|
||||
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
class PlatformTestCase(test.TestCase):
|
||||
|
||||
def test_plugin_configure_and_methods(self):
|
||||
|
||||
@platform.configure(name="existing", platform="foo")
|
||||
class FooPlugin(platform.Platform):
|
||||
pass
|
||||
|
||||
self.addCleanup(FooPlugin.unregister)
|
||||
|
||||
f = FooPlugin("spec", "uuid", "plugin_data", "platform_data", "status")
|
||||
self.assertEqual(f.uuid, "uuid")
|
||||
self.assertEqual(f.spec, "spec")
|
||||
self.assertEqual(f.plugin_data, "plugin_data")
|
||||
self.assertEqual(f.platform_data, "platform_data")
|
||||
self.assertEqual(f.status, "status")
|
||||
|
||||
self.assertRaises(NotImplementedError, f.create)
|
||||
self.assertRaises(NotImplementedError, f.destroy)
|
||||
self.assertRaises(NotImplementedError, f.update, "new_spec")
|
||||
self.assertRaises(NotImplementedError, f.cleanup)
|
||||
self.assertRaises(NotImplementedError,
|
||||
f.cleanup, task_uuid="task_uuid")
|
||||
self.assertRaises(NotImplementedError, f.check_health)
|
||||
self.assertRaises(NotImplementedError, f.info)
|
Loading…
x
Reference in New Issue
Block a user