Added Backend API Database Implementation

Added a SQLite backed backend API implementation
that interfaces with the database using sqlalchemy.
All of the unit tests associated are included as
well.

Change-Id: I0948fe85d381db4ef1305ed6234ca3e06f3c8760
This commit is contained in:
kchenweijie
2013-08-07 16:36:17 -05:00
parent 4cee45e2e3
commit ff1dc9544c
8 changed files with 1427 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
# Copyright (C) 2013 Rackspace Hosting 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.

View File

@@ -0,0 +1,484 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
# Copyright (C) 2013 Rackspace Hosting 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.
"""Implementation of SQLAlchemy backend."""
import logging
from sqlalchemy import exc
from taskflow.backends.sqlalchemy import models
from taskflow.backends.sqlalchemy import session as sql_session
from taskflow import exceptions as exception
from taskflow.generics import flowdetail
from taskflow.generics import logbook
from taskflow.generics import taskdetail
LOG = logging.getLogger(__name__)
def model_query(*args, **kwargs):
session = kwargs.get('session') or sql_session.get_session()
query = session.query(*args)
return query
"""
LOGBOOK
"""
def logbook_create(name, lb_id=None):
"""Creates a new LogBook model with matching lb_id"""
# Create a LogBook model to save
lb_ref = models.LogBook()
# Update attributes of the LogBook model
lb_ref.name = name
if lb_id:
lb_ref.logbook_id = lb_id
# Save the LogBook to the database
lb_ref.save()
def logbook_destroy(lb_id):
"""Deletes the LogBook model with matching lb_id"""
# Get the session to interact with the database
session = sql_session.get_session()
with session.begin():
# Get the LogBook model
lb = _logbook_get_model(lb_id, session=session)
# Delete the LogBook model from the database
lb.delete(session=session)
def logbook_save(lb):
"""Saves a generic LogBook object to the db"""
# Try to create the LogBook model
try:
logbook_create(lb.name, lb.uuid)
# Do nothing if it is already there
except exc.IntegrityError:
pass
# Get a copy of the LogBook in the database
db_lb = logbook_get(lb.uuid)
for fd in lb:
# Save each FlowDetail
flowdetail_save(fd)
# Add the FlowDetail model to the LogBook model if it is not there
if fd not in db_lb:
logbook_add_flow_detail(lb.uuid, fd.uuid)
def logbook_delete(lb):
"""Deletes a LogBook from db based on a generic type"""
# Get a session to interact with the database
session = sql_session.get_session()
with session.begin():
# Get the LogBook model
lb_model = _logbook_get_model(lb.uuid, session=session)
# Raise an error if the LogBook model still has FlowDetails
if lb_model.flowdetails:
raise exception.Error("Logbook <%s> still has "
"dependents." % (lb.uuid,))
# Destroy the model if it is safe
else:
logbook_destroy(lb.uuid)
def logbook_get(lb_id):
"""Gets a LogBook with matching lb_id, if it exists"""
# Get a session to interact with the database
session = sql_session.get_session()
with session.begin():
# Get the LogBook model from the database
lb = _logbook_get_model(lb_id, session=session)
# Create a generic LogBook to return
retVal = logbook.LogBook(lb.name, lb.logbook_id)
# Add the generic FlowDetails associated with this LogBook
for fd in lb.flowdetails:
retVal.add_flow_detail(flowdetail_get(fd.flowdetail_id))
return retVal
def logbook_add_flow_detail(lb_id, fd_id):
"""Adds a FlowDetail with id fd_id to a LogBook with id lb_id"""
# Get a session to interact with the database
session = sql_session.get_session()
with session.begin():
# Get the LogBook model from the database
lb = _logbook_get_model(lb_id, session=session)
# Get the FlowDetail model from the database
fd = _flowdetail_get_model(fd_id, session=session)
# Add the FlowDetail model to the LogBook model
lb.flowdetails.append(fd)
def logbook_remove_flowdetail(lb_id, fd_id):
"""Removes a FlowDetail with id fd_id from a LogBook with id lb_id"""
# Get a session to interact with the database
session = sql_session.get_session()
with session.begin():
# Get the LogBook model
lb = _logbook_get_model(lb_id, session=session)
# Remove the FlowDetail model from the LogBook model
lb.flowdetails = [fd for fd in lb.flowdetails
if fd.flowdetail_id != fd_id]
def logbook_get_ids_names():
"""Returns all LogBook ids and names"""
# Get a List of all LogBook models
lbs = model_query(models.LogBook).all()
# Get all of the LogBook uuids
lb_ids = [lb.logbook_id for lb in lbs]
# Get all of the LogBook names
names = [lb.name for lb in lbs]
# Return a dict with uuids and names
return dict(zip(lb_ids, names))
def _logbook_get_model(lb_id, session=None):
"""Gets a LogBook model with matching lb_id, if it exists"""
# Get a query of LogBooks by uuid
query = model_query(models.LogBook, session=session).\
filter_by(logbook_id=lb_id)
# If there are no elements in the Query, raise a NotFound exception
if not query.first():
raise exception.NotFound("No LogBook found with id "
"%s." % (lb_id,))
# Return the first item in the Query
return query.first()
def _logbook_exists(lb_id, session=None):
"""Check if a LogBook with lb_id exists"""
# Gets a Query of all LogBook models
query = model_query(models.LogBook, session=session).\
filter_by(logbook_id=lb_id)
# Return False if the query is empty
if not query.first():
return False
# Return True if there is something in the query
return True
"""
FLOWDETAIL
"""
def flowdetail_create(name, wf, fd_id=None):
"""Create a new FlowDetail model with matching fd_id"""
# Create a FlowDetail model to be saved
fd_ref = models.FlowDetail()
# Update attributes of FlowDetail model to be saved
fd_ref.name = name
if fd_id:
fd_ref.flowdetail_id = fd_id
# Save FlowDetail model to database
fd_ref.save()
def flowdetail_destroy(fd_id):
"""Deletes the FlowDetail model with matching fd_id"""
# Get a session for interaction with the database
session = sql_session.get_session()
with session.begin():
# Get the FlowDetail model
fd = _flowdetail_get_model(fd_id, session=session)
# Delete the FlowDetail from the database
fd.delete(session=session)
def flowdetail_save(fd):
"""Saves a generic FlowDetail object to the db"""
# Try to create the FlowDetail model
try:
flowdetail_create(fd.name, fd.flow, fd.uuid)
# Do nothing if it is already there
except exc.IntegrityError:
pass
# Get a copy of the FlowDetail in the database
db_fd = flowdetail_get(fd.uuid)
for td in fd:
# Save each TaskDetail
taskdetail_save(td)
# Add the TaskDetail model to the FlowDetail model if it is not there
if td not in db_fd:
flowdetail_add_task_detail(fd.uuid, td.uuid)
def flowdetail_delete(fd):
"""Deletes a FlowDetail from db based on a generic type"""
# Get a session to interact with the database
session = sql_session.get_session()
with session.begin():
# Get the FlowDetail model
fd_model = _flowdetail_get_model(fd.uuid, session=session)
# Raise an error if the FlowDetail model still has TaskDetails
if fd_model.taskdetails:
raise exception.Error("FlowDetail <%s> still has "
"dependents." % (fd.uuid,))
# If it is safe, destroy the FlowDetail model from the database
else:
flowdetail_destroy(fd.uuid)
def flowdetail_get(fd_id):
"""Gets a FlowDetail with matching fd_id, if it exists"""
# Get a session for interaction with the database
session = sql_session.get_session()
with session.begin():
# Get the FlowDetail model from the database
fd = _flowdetail_get_model(fd_id, session=session)
# Create a generic FlowDetail to return
retVal = flowdetail.FlowDetail(fd.name, None, fd.flowdetail_id)
# Update attributes to match
retVal.updated_at = fd.updated_at
# Add the TaskDetails belonging to this FlowDetail to itself
for td in fd.taskdetails:
retVal.add_task_detail(taskdetail_get(td.taskdetail_id))
return retVal
def flowdetail_add_task_detail(fd_id, td_id):
"""Adds a TaskDetail with id td_id to a Flowdetail with id fd_id"""
# Get a session for interaction with the database
session = sql_session.get_session()
with session.begin():
# Get the FlowDetail model
fd = _flowdetail_get_model(fd_id, session=session)
# Get the TaskDetail model
td = _taskdetail_get_model(td_id, session=session)
# Add the TaskDetail model to the FlowDetail model
fd.taskdetails.append(td)
def flowdetail_remove_taskdetail(fd_id, td_id):
"""Removes a TaskDetail with id td_id from a FlowDetail with id fd_id"""
# Get a session for interaction with the database
session = sql_session.get_session()
with session.begin():
# Get the FlowDetail model
fd = _flowdetail_get_model(fd_id, session=session)
# Remove the TaskDetail from the FlowDetail model
fd.taskdetails = [td for td in fd.taskdetails
if td.taskdetail_id != td_id]
def flowdetail_get_ids_names():
"""Returns all FlowDetail ids and names"""
# Get all FlowDetail models
fds = model_query(models.FlowDetail).all()
# Get the uuids of all FlowDetail models
fd_ids = [fd.flowdetail_id for fd in fds]
# Get the names of all FlowDetail models
names = [fd.name for fd in fds]
# Return a dict of uuids and names
return dict(zip(fd_ids, names))
def _flowdetail_get_model(fd_id, session=None):
"""Gets a FlowDetail model with matching fd_id, if it exists"""
# Get a query of FlowDetails by uuid
query = model_query(models.FlowDetail, session=session).\
filter_by(flowdetail_id=fd_id)
# Raise a NotFound exception if the query is empty
if not query.first():
raise exception.NotFound("No FlowDetail found with id "
"%s." % (fd_id,))
# Return the first entry in the query
return query.first()
def _flowdetail_exists(fd_id, session=None):
"""Checks if a FlowDetail with fd_id exists"""
# Get a query of FlowDetails by uuid
query = model_query(models.FlowDetail, session=session).\
filter_by(flowdetail_id=fd_id)
# Return False if the query is empty
if not query.first():
return False
# Return True if there is something in the query
return True
"""
TASKDETAIL
"""
def taskdetail_create(name, tsk, td_id=None):
"""Create a new TaskDetail model with matching td_id"""
# Create a TaskDetail model to add
td_ref = models.TaskDetail()
# Update the attributes of the TaskDetail model to add
td_ref.name = name
if td_id:
td_ref.taskdetail_id = td_id
td_ref.task_id = tsk.uuid
td_ref.task_name = tsk.name
td_ref.task_provides = list(tsk.provides)
td_ref.task_requires = list(tsk.requires)
td_ref.task_optional = list(tsk.optional)
# Save the TaskDetail model to the database
td_ref.save()
def taskdetail_destroy(td_id):
"""Deletes the TaskDetail model with matching td_id"""
# Get a session for interaction with the database
session = sql_session.get_session()
with session.begin():
# Get the TaskDetail model to delete
td = _taskdetail_get_model(td_id, session=session)
# Delete the TaskDetail model from the database
td.delete(session=session)
def taskdetail_save(td):
"""Saves a generic TaskDetail object to the db"""
# Create a TaskDetail model if it does not already exist
if not _taskdetail_exists(td.uuid):
taskdetail_create(td.name, td.task, td.uuid)
# Prepare values to be saved to the TaskDetail model
values = dict(state=td.state,
results=td.results,
exception=td.exception,
stacktrace=td.stacktrace,
meta=td.meta)
# Update the TaskDetail model with the values of the generic TaskDetail
taskdetail_update(td.uuid, values)
def taskdetail_delete(td):
"""Deletes a TaskDetail from db based on a generic type"""
# Destroy the TaskDetail if it exists
taskdetail_destroy(td.uuid)
def taskdetail_get(td_id):
"""Gets a TaskDetail with matching td_id, if it exists"""
# Get a session for interaction with the database
session = sql_session.get_session()
with session.begin():
# Get the TaskDetail model
td = _taskdetail_get_model(td_id, session=session)
# Create a generic type Task to return as part of the TaskDetail
tsk = None
# Create a generic type TaskDetail to return
retVal = taskdetail.TaskDetail(td.name, tsk, td.taskdetail_id)
# Update the TaskDetail to reflect the data in the database
retVal.updated_at = td.updated_at
retVal.state = td.state
retVal.results = td.results
retVal.exception = td.exception
retVal.stacktrace = td.stacktrace
retVal.meta = td.meta
return retVal
def taskdetail_update(td_id, values):
"""Updates a TaskDetail with matching td_id"""
# Get a session for interaction with the database
session = sql_session.get_session()
with session.begin():
# Get the TaskDetail model
td = _taskdetail_get_model(td_id, session=session)
# Update the TaskDetail model with values
td.update(values)
# Write the TaskDetail model changes to the database
td.save(session=session)
def taskdetail_get_ids_names():
"""Returns all TaskDetail ids and names"""
# Get all TaskDetail models
tds = model_query(models.TaskDetail).all()
# Get the list of TaskDetail uuids
td_ids = [td.taskdetail_id for td in tds]
# Get the list of TaskDetail names
names = [td.name for td in tds]
#Return a dict of uuids and names
return dict(zip(td_ids, names))
def _taskdetail_get_model(td_id, session=None):
"""Gets a TaskDetail model with matching td_id, if it exists"""
# Get a query of TaskDetails by uuid
query = model_query(models.TaskDetail, session=session).\
filter_by(taskdetail_id=td_id)
# Raise a NotFound exception if the query is empty
if not query.first():
raise exception.NotFound("No TaskDetail found with id "
"%s." % (td_id,))
return query.first()
def _taskdetail_exists(td_id, session=None):
"""Check if a TaskDetail with td_id exists"""
# Get a query of TaskDetails by uuid
query = model_query(models.TaskDetail, session=session).\
filter_by(taskdetail_id=td_id)
# Return False if the query is empty
if not query.first():
return False
# Return True if there is something in the query
return True

View File

@@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
# Copyright (C) 2013 Rackspace Hosting 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.
"""
SQLAlchemy models for taskflow data.
"""
import json
from oslo.config import cfg
from sqlalchemy import Column, Integer, String
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import object_mapper, relationship
from sqlalchemy import DateTime, ForeignKey
from sqlalchemy import types as types
from taskflow.backends.sqlalchemy import session as sql_session
from taskflow import exceptions as exception
from taskflow.openstack.common import timeutils
from taskflow.openstack.common import uuidutils
CONF = cfg.CONF
BASE = declarative_base()
class Json(types.TypeDecorator, types.MutableType):
impl = types.Text
def process_bind_param(self, value, dialect):
return json.dumps(value)
def process_result_value(self, value, dialect):
return json.loads(value)
class TaskFlowBase(object):
"""Base class for TaskFlow Models."""
__table_args__ = {'mysql_engine': 'InnoDB'}
__table_initialized = False
created_at = Column(DateTime, default=timeutils.utcnow)
updated_at = Column(DateTime, default=timeutils.utcnow)
def save(self, session=None):
"""Save this object."""
if not session:
session = sql_session.get_session()
session.add(self)
try:
session.flush()
except IntegrityError, e:
if str(e).endswith('is not unique'):
raise exception.Duplicate(str(e))
else:
raise
def delete(self, session=None):
"""Delete this object."""
self.deleted = True
self.deleted_at = timeutils.utcnow()
if not session:
session = sql_session.get_session()
session.delete(self)
session.flush()
def __setitem__(self, key, value):
setattr(self, key, value)
def __getitem__(self, key):
return getattr(self, key)
def get(self, key, default=None):
return getattr(self, key, default)
def __iter__(self):
self._i = iter(object_mapper(self).columns)
return self
def next(self):
n = self._i.next().name
return n, getattr(self, n)
def update(self, values):
"""Make the model object behave like a dict"""
for k, v in values.iteritems():
setattr(self, k, v)
def iteritems(self):
"""Make the model object behave like a dict
Includes attributes from joins.
"""
local = dict(self)
joined = dict([k, v] for k, v in self.__dict__.iteritems()
if not k[0] == '_')
local.update(joined)
return local.iteritems()
class LogBook(BASE, TaskFlowBase):
"""Represents a logbook for a set of flows"""
__tablename__ = 'logbook'
# Member variables
id = Column(Integer, primary_key=True)
logbook_id = Column(String, default=uuidutils.generate_uuid,
unique=True)
name = Column(String)
# Relationships
flowdetails = relationship("FlowDetail", backref="logbook")
class FlowDetail(BASE, TaskFlowBase):
"""Represent FlowDetail objects"""
__tablename__ = 'flowdetail'
# Member variables
id = Column(Integer, primary_key=True)
flowdetail_id = Column(String, default=uuidutils.generate_uuid,
unique=True)
name = Column(String)
flow_id = Column(String)
flow_type = Column(String)
# Relationships
logbook_id = Column(Integer, ForeignKey('logbook.logbook_id'))
taskdetails = relationship("TaskDetail", backref="flowdetail")
class TaskDetail(BASE, TaskFlowBase):
"""Represents TaskDetail objects"""
__tablename__ = 'taskdetail'
# Member variables
id = Column(Integer, primary_key=True)
taskdetail_id = Column(String, default=uuidutils.generate_uuid,
unique=True)
name = Column(String)
state = Column(String)
results = Column(Json)
exception = Column(String)
stacktrace = Column(String)
meta = Column(String)
task_id = Column(String)
task_name = Column(String)
task_provides = Column(Json)
task_requires = Column(Json)
task_optional = Column(Json)
# Relationships
flowdetail_id = Column(String, ForeignKey('flowdetail.flowdetail_id'))
def create_tables():
BASE.metadata.create_all(sql_session.get_engine())

View File

@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
# Copyright (C) 2013 Rackspace Hosting 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.
"""Session Handling for SQLAlchemy backend."""
import logging
import sqlalchemy
import sqlalchemy.engine
import sqlalchemy.interfaces
import sqlalchemy.orm
from sqlalchemy.pool import NullPool
from sqlalchemy import exc
from taskflow.backends import api as b_api
LOG = logging.getLogger(__name__)
_ENGINE = None
_MAKER = None
def get_session(autocommit=True, expire_on_commit=True):
"""Return a SQLAlchemy session."""
global _MAKER
if _MAKER is None:
_MAKER = get_maker(get_engine(), autocommit, expire_on_commit)
return _MAKER()
def synchronous_switch_listener(dbapi_conn, connection_rec):
"""Switch sqlite connections to non-synchronous mode"""
dbapi_conn.execute("PRAGMA synchronous = OFF")
def ping_listener(dbapi_conn, connection_rec, connection_proxy):
"""Ensures that MySQL connections checked out of the
pool are alive.
Borrowed from:
http://groups.google.com/group/sqlalchemy/msg/a4ce563d802c929f
"""
try:
dbapi_conn.cursor().execute('select 1')
except dbapi_conn.OperationalError, ex:
if ex.args[0] in (2006, 2013, 2014, 2045, 2055):
LOG.warn(_('Got mysql server has gone away: %s'), ex)
raise exc.DisconnectionError("Database server went away")
else:
raise
def get_engine():
"""Return a SQLAlchemy engine."""
global _ENGINE
if _ENGINE is None:
connection_dict = sqlalchemy.engine.url.make_url(_get_sql_connection())
engine_args = {
"pool_recycle": _get_sql_idle_timeout(),
"echo": False,
"convert_unicode": True
}
if "sqlite" in connection_dict.drivername:
engine_args['poolclass'] = NullPool
_ENGINE = sqlalchemy.create_engine(_get_sql_connection(),
**engine_args)
if 'mysql' in connection_dict.drivername:
sqlalchemy.event.listen(_ENGINE, 'checkout', ping_listener)
if 'sqlite' in connection_dict.drivername:
sqlalchemy.event.listen(_ENGINE, 'connect',
synchronous_switch_listener)
# TODO(jharlow): Check to make sure engine connected
return _ENGINE
def get_maker(engine, autocommit=True, expire_on_commit=False):
"Return a SQLAlchemy sessionmaker using the given engine."""
return sqlalchemy.orm.sessionmaker(bind=engine,
autocommit=autocommit,
expire_on_commit=expire_on_commit)
def _get_sql_connection():
return b_api.SQL_CONNECTION
def _get_sql_idle_timeout():
return b_api.SQL_IDLE_TIMEOUT

View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
# Copyright (C) 2013 Rackspace Hosting 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 os
from os import path
from taskflow.backends import api as b_api
from taskflow.backends.sqlalchemy import models
def setUpModule():
b_api.configure('db_backend')
b_api.SQL_CONNECTION = 'sqlite:///test.db'
if not path.isfile('test.db'):
models.create_tables()
def tearDownModule():
os.remove('test.db')

View File

@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2013 Rackspace Hosting 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 required libraries"""
import unittest2
from taskflow.backends import api as b_api
from taskflow import exceptions as exception
from taskflow.generics import flowdetail
from taskflow.openstack.common import uuidutils
from taskflow.patterns import graph_flow as flow
from taskflow.tests import utils
class FlowDetailTest(unittest2.TestCase):
"""This class is to test the functionality of the backend API's flowdetail
methods.
"""
wfs = []
fd_names = []
fd_ids = []
tsks = []
td_names = []
td_ids = []
@classmethod
def setUpClass(cls):
# Create a workflow for flowdetails to use
wf_id = uuidutils.generate_uuid()
wf_name = 'wf-%s' % (wf_id)
wf = flow.Flow(wf_name, None, wf_id)
cls.wfs.append(wf)
# Create a task for taskdetails to use
task_id = uuidutils.generate_uuid()
task_name = 'task-%s' % (task_id)
tsk = utils.DummyTask(task_name, task_id)
cls.tsks.append(tsk)
@classmethod
def tearDownClass(cls):
# Clear out the lists of workflows and tasks
utils.drain(cls.wfs)
utils.drain(cls.tsks)
def setUp(self):
# Create a flowdetail and record its uuid and name
fd_id = uuidutils.generate_uuid()
fd_name = 'fd-%s' % (fd_id)
b_api.flowdetail_create(fd_name, self.wfs[0], fd_id)
self.fd_names.append(fd_name)
self.fd_ids.append(fd_id)
# Create a taskdetail and record its uuid and name
td_id = uuidutils.generate_uuid()
td_name = 'td-%s' % (td_id)
b_api.taskdetail_create(td_name, self.tsks[0], td_id)
self.td_names.append(td_name)
self.td_ids.append(td_id)
def tearDown(self):
# Destroy all taskdetails and flowdetails in the backend
for id in self.td_ids:
b_api.taskdetail_destroy(id)
for id in self.fd_ids:
b_api.flowdetail_destroy(id)
# Drain the lists of taskdetail and flowdetail uuids and names
utils.drain(self.fd_names)
utils.drain(self.fd_ids)
utils.drain(self.td_names)
utils.drain(self.td_ids)
def test_flowdetail_create(self):
# Create a flowdetail and record its uuid and name
fd_id = uuidutils.generate_uuid()
fd_name = 'fd-%s' % (fd_id)
b_api.flowdetail_create(fd_name, self.wfs[0], fd_id)
self.fd_names.append(fd_name)
self.fd_ids.append(fd_id)
# Check to see that the created flowdetail is there
actual = b_api.flowdetail_get(fd_id)
self.assertIsNotNone(actual)
def test_flowdetail_destroy(self):
# Destroy the last added flowdetail
id = self.fd_ids.pop()
b_api.flowdetail_destroy(id)
self.fd_names.pop()
# Check to make sure the removed flowdetail is no longer there
self.assertRaises(exception.NotFound, b_api.flowdetail_get,
id)
def test_flowdetail_save(self):
# Create a generic flowdetail to save
fd_id = uuidutils.generate_uuid()
fd_name = 'fd-%s' % (fd_id)
wf = self.wfs[0]
fd = flowdetail.FlowDetail(fd_name, wf, fd_id)
# Save the generic flowdetail to the backend and record its uuid/name
b_api.flowdetail_save(fd)
self.fd_names.append(fd_name)
self.fd_ids.append(fd_id)
# Check that the saved flowdetail is in the backend
actual = b_api.flowdetail_get(fd_id)
self.assertIsNotNone(actual)
# Check that the saved flowdetail has no taskdetails
self.assertEquals(len(actual), 0)
# Add a generic taskdetail to the flowdetail
td = b_api.taskdetail_get(self.td_ids[0])
fd.add_task_detail(td)
# Save the updated flowdetail
b_api.flowdetail_save(fd)
# Check that the saved flowdetail is still there
actual = b_api.flowdetail_get(fd_id)
self.assertIsNotNone(actual)
# Check that the addition of a taskdetail was recorded
self.assertEquals(len(actual), 1)
def test_flowdetail_delete(self):
# Get the flowdetail to delete
id = self.fd_ids.pop()
fd = b_api.flowdetail_get(id)
# Delete the flowdetail
b_api.flowdetail_delete(fd)
self.fd_names.pop()
# Make sure it is not there anymore
self.assertRaises(exception.NotFound, b_api.flowdetail_get,
id)
def test_flowdetail_get(self):
# Get the first flowdetail
actual = b_api.flowdetail_get(self.fd_ids[0])
# Check that it is a flowdetail
self.assertIsInstance(actual, flowdetail.FlowDetail)
# Check that its name matches what is expected
self.assertEquals(actual.name, self.fd_names[0])
def test_flowdetail_add_task_detail(self):
# Get the first flowdetail
actual = b_api.flowdetail_get(self.fd_ids[0])
# Make sure it has no taskdetails
self.assertEquals(len(actual), 0)
# Add a taskdetail to the flowdetail
b_api.flowdetail_add_task_detail(self.fd_ids[0], self.td_ids[0])
# Get the flowdetail again
actual = b_api.flowdetail_get(self.fd_ids[0])
# Check that the flowdetail has one taskdetail
self.assertEquals(len(actual), 1)
def test_flowdetail_remove_taskdetail(self):
# Add a taskdetail to the first flowdetail
b_api.flowdetail_add_task_detail(self.fd_ids[0], self.td_ids[0])
# Get the first flowdetail
actual = b_api.flowdetail_get(self.fd_ids[0])
# Check that the first flowdetail has exactly one taskdetail
self.assertEquals(len(actual), 1)
# Remove the taskdetail from the first flowdetail
b_api.flowdetail_remove_taskdetail(self.fd_ids[0], self.td_ids[0])
# Get the first flowdetail
actual = b_api.flowdetail_get(self.fd_ids[0])
# Check that the first flowdetail no longer has any taskdetails
self.assertEquals(len(actual), 0)
def test_flowdetail_get_ids_names(self):
# Get a list of all uuids and names for flowdetails
actual = b_api.flowdetail_get_ids_names()
# Match it to our in-memory records
self.assertEquals(actual.values(), self.fd_names)
self.assertEquals(actual.keys(), self.fd_ids)

View File

@@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2013 Rackspace Hosting 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 required libraries"""
import unittest2
from taskflow.backends import api as b_api
from taskflow import exceptions as exception
from taskflow.generics import logbook
from taskflow.openstack.common import uuidutils
from taskflow.patterns import graph_flow as flow
from taskflow.tests import utils
class LogBookTest(unittest2.TestCase):
"""This class is designed to test the functionality of the backend API's
logbook methods
"""
lb_names = []
lb_ids = []
wfs = []
fd_names = []
fd_ids = []
@classmethod
def setUpClass(cls):
# Create a workflow to create flowdetails with
wf_id = uuidutils.generate_uuid()
wf_name = 'wf-%s' % (wf_id)
wf = flow.Flow(wf_name, None, wf_id)
cls.wfs.append(wf)
@classmethod
def tearDownClass(cls):
# Empty the list of workflows
utils.drain(cls.wfs)
def setUp(self):
# Create a logbook and record its uuid and name
lb_id = uuidutils.generate_uuid()
lb_name = 'lb-%s' % (lb_id)
b_api.logbook_create(lb_name, lb_id)
self.lb_names.append(lb_name)
self.lb_ids.append(lb_id)
# Create a flowdetail and record its uuid and name
fd_id = uuidutils.generate_uuid()
fd_name = 'fd-%s' % (fd_id)
b_api.flowdetail_create(fd_name, self.wfs[0], fd_id)
self.fd_names.append(fd_name)
self.fd_ids.append(fd_id)
def tearDown(self):
# Destroy all flowdetails and logbooks in the backend
for id in self.fd_ids:
b_api.flowdetail_destroy(id)
for id in self.lb_ids:
b_api.logbook_destroy(id)
# Clear the lists of logbook and flowdetail uuids and names
utils.drain(self.lb_names)
utils.drain(self.lb_ids)
utils.drain(self.fd_names)
utils.drain(self.fd_ids)
def test_logbook_create(self):
# Create a logbook and record its uuid and name
lb_id = uuidutils.generate_uuid()
lb_name = 'lb-%s' % (lb_id)
b_api.logbook_create(lb_name, lb_id)
self.lb_names.append(lb_name)
self.lb_ids.append(lb_id)
# Check that the created logbook exists in the backend
actual = b_api.logbook_get(lb_id)
self.assertIsNotNone(actual)
def test_logbook_destroy(self):
# Delete the last added logbook
id = self.lb_ids.pop()
b_api.logbook_destroy(id)
self.lb_names.pop()
# Check that the deleted logbook is no longer there
self.assertRaises(exception.NotFound, b_api.logbook_get,
id)
def test_logbook_save(self):
# Create a generic logbook to save
lb_id = uuidutils.generate_uuid()
lb_name = 'lb-%s' % (lb_id)
lb = logbook.LogBook(lb_name, lb_id)
# Save the logbook and record its uuid and name
b_api.logbook_save(lb)
self.lb_names.append(lb_name)
self.lb_ids.append(lb_id)
# Check that the saved logbook exists in the backend
actual = b_api.logbook_get(lb_id)
self.assertIsNotNone(actual)
# Check that the saved logbook has no flowdetails
self.assertEquals(len(actual), 0)
# Add a flowdetail to the logbook
fd = b_api.flowdetail_get(self.fd_ids[0])
lb.add_flow_detail(fd)
# Save the updated logbook
b_api.logbook_save(lb)
# Check that the updated logbook is still in the backend
actual = b_api.logbook_get(lb_id)
self.assertIsNotNone(actual)
# Check that the added flowdetail was recorded
self.assertEquals(len(actual), 1)
def test_logbook_delete(self):
# Get the logbook to delete
id = self.lb_ids.pop()
lb = b_api.logbook_get(id)
# Delete the logbook from the backend
b_api.logbook_delete(lb)
self.lb_names.pop()
# Check that the deleted logbook is no longer present
self.assertRaises(exception.NotFound, b_api.logbook_get,
id)
def test_logbook_get(self):
# Get the logbook from the backend
actual = b_api.logbook_get(self.lb_ids[0])
# Check that it is actually a logbook
self.assertIsInstance(actual, logbook.LogBook)
# Check that the name is correct
self.assertEquals(actual.name, self.lb_names[0])
def test_logbook_add_flow_detail(self):
# Get the logbook from the backend
actual = b_api.logbook_get(self.lb_ids[0])
# Check that it has no flowdetails
self.assertEquals(len(actual), 0)
# Add a flowdetail to the logbook
b_api.logbook_add_flow_detail(self.lb_ids[0], self.fd_ids[0])
# Get the logbook again
actual = b_api.logbook_get(self.lb_ids[0])
# Check that the logbook has exactly one flowdetail
self.assertEquals(len(actual), 1)
def test_logbook_remove_flowdetail(self):
# Add a flowdetail to the first logbook
b_api.logbook_add_flow_detail(self.lb_ids[0], self.fd_ids[0])
# Get the first logbook
actual = b_api.logbook_get(self.lb_ids[0])
# Check that it has exactly one flowdetail
self.assertEquals(len(actual), 1)
# Remove the flowdetail from the logbook
b_api.logbook_remove_flowdetail(self.lb_ids[0], self.fd_ids[0])
# Get the logbook again
actual = b_api.logbook_get(self.lb_ids[0])
# Check that the logbook now has no flowdetails
self.assertEquals(len(actual), 0)
def test_logbook_get_ids_names(self):
# Get the dict of uuids and names
actual = b_api.logbook_get_ids_names()
# Check that it matches our in-memory list
self.assertEquals(actual.values(), self.lb_names)
self.assertEquals(actual.keys(), self.lb_ids)

View File

@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2013 Rackspace Hosting 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 required libraries"""
import unittest2
from taskflow.backends import api as b_api
from taskflow import exceptions as exception
from taskflow.generics import taskdetail
from taskflow.openstack.common import uuidutils
from taskflow.tests import utils
class TaskDetailTest(unittest2.TestCase):
"""This class is designed to test the functionality of the backend API's
taskdetail methods
"""
tsks = []
td_names = []
td_ids = []
@classmethod
def setUpClass(cls):
# Create a task for taskdetails to be made from
task_id = uuidutils.generate_uuid()
task_name = 'task-%s' % (task_id)
tsk = utils.DummyTask(task_name, task_id)
tsk.requires.update('r')
tsk.optional.update('o')
tsk.provides.update('p')
cls.tsks.append(tsk)
@classmethod
def tearDownClass(cls):
# Clear the tasks
utils.drain(cls.tsks)
def setUp(self):
# Create a taskdetail and record its uuid and name
td_id = uuidutils.generate_uuid()
td_name = 'td-%s' % (td_id)
b_api.taskdetail_create(td_name, self.tsks[0], td_id)
self.td_names.append(td_name)
self.td_ids.append(td_id)
def tearDown(self):
# Destroy all taskdetails from the backend
for id in self.td_ids:
b_api.taskdetail_destroy(id)
# Clear the list of taskdetail names and uuids
utils.drain(self.td_names)
utils.drain(self.td_ids)
def test_taskdetail_create(self):
# Create a taskdetail and record its uuid and name
td_id = uuidutils.generate_uuid()
td_name = 'td-%s' % (td_id)
b_api.taskdetail_create(td_name, self.tsks[0], td_id)
self.td_names.append(td_name)
self.td_ids.append(td_id)
# Check that the taskdetail is there
actual = b_api.taskdetail_get(td_id)
self.assertIsNotNone(actual)
def test_taskdetail_destroy(self):
# Destroy the last added taskdetail
id = self.td_ids.pop()
b_api.taskdetail_destroy(id)
self.td_names.pop()
# Check that the deleted taskdetail is no longer there
self.assertRaises(exception.NotFound, b_api.taskdetail_get,
id)
def test_taskdetail_save(self):
# Create a generic taskdetail to save
td_id = uuidutils.generate_uuid()
td_name = 'td-%s' % (td_id)
tsk = self.tsks[0]
td = taskdetail.TaskDetail(td_name, tsk, td_id)
# Save the generic taskdetail to the backend and record uuid/name
b_api.taskdetail_save(td)
self.td_names.append(td_name)
self.td_ids.append(td_id)
# Get the created taskdetail and check for default attributes
actual = b_api.taskdetail_get(td_id)
self.assertIsNotNone(actual)
self.assertIsNone(actual.state)
self.assertIsNone(actual.results)
self.assertIsNone(actual.exception)
self.assertIsNone(actual.stacktrace)
self.assertIsNone(actual.meta)
# Change the generic taskdetail's attributes
td.state = 'SUCCESS'
td.exception = 'ERROR'
td.stacktrace = 'STACKTRACE'
td.meta = 'META'
# Save the changed taskdetail
b_api.taskdetail_save(td)
# Get the updated taskdetail and check for updated attributes
actual = b_api.taskdetail_get(td_id)
self.assertEquals(actual.state, 'SUCCESS')
self.assertIsNone(actual.results)
self.assertEquals(actual.exception, 'ERROR')
self.assertEquals(actual.stacktrace, 'STACKTRACE')
self.assertEquals(actual.meta, 'META')
def test_taskdetail_delete(self):
# Get the taskdetail to delete
id = self.td_ids.pop()
td = b_api.taskdetail_get(id)
# Delete the desired taskdetail
b_api.taskdetail_delete(td)
self.td_names.pop()
# Check that the deleted taskdetail is no longer there
self.assertRaises(exception.NotFound, b_api.taskdetail_get,
id)
def test_taskdetail_get(self):
# Get the first taskdetail
actual = b_api.taskdetail_get(self.td_ids[0])
# Check that it is actually a taskdetail
self.assertIsInstance(actual, taskdetail.TaskDetail)
# Check that its name is what we expect
self.assertEquals(actual.name, self.td_names[0])
def test_taskdetail_update(self):
# Get the first taskdetail and check for default attributes
actual = b_api.taskdetail_get(self.td_ids[0])
self.assertIsNone(actual.state)
self.assertIsNone(actual.results)
self.assertIsNone(actual.exception)
self.assertIsNone(actual.stacktrace)
self.assertIsNone(actual.meta)
# Prepare attributes for updating
values = dict(state='SUCCESS', exception='ERROR',
stacktrace='STACKTRACE', meta='META')
# Update attributes
b_api.taskdetail_update(self.td_ids[0], values)
# Get the updated taskdetila and check for updated attributes
actual = b_api.taskdetail_get(self.td_ids[0])
self.assertEquals(actual.state, 'SUCCESS')
self.assertIsNone(actual.results)
self.assertEquals(actual.exception, 'ERROR')
self.assertEquals(actual.stacktrace, 'STACKTRACE')
self.assertEquals(actual.meta, 'META')
def test_taskdetail_get_ids_names(self):
# Get dict of uuids and names
actual = b_api.taskdetail_get_ids_names()
# Check that it matches our in-memory records
self.assertEquals(actual.values(), self.td_names)
self.assertEquals(actual.keys(), self.td_ids)