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:
18
taskflow/backends/sqlalchemy/__init__.py
Normal file
18
taskflow/backends/sqlalchemy/__init__.py
Normal 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.
|
||||
484
taskflow/backends/sqlalchemy/api.py
Normal file
484
taskflow/backends/sqlalchemy/api.py
Normal 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
|
||||
174
taskflow/backends/sqlalchemy/models.py
Normal file
174
taskflow/backends/sqlalchemy/models.py
Normal 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())
|
||||
111
taskflow/backends/sqlalchemy/session.py
Normal file
111
taskflow/backends/sqlalchemy/session.py
Normal 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
|
||||
36
taskflow/tests/unit/sql_db_api/__init__.py
Normal file
36
taskflow/tests/unit/sql_db_api/__init__.py
Normal 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')
|
||||
212
taskflow/tests/unit/sql_db_api/test_flowdetail_api.py
Normal file
212
taskflow/tests/unit/sql_db_api/test_flowdetail_api.py
Normal 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)
|
||||
202
taskflow/tests/unit/sql_db_api/test_logbook_api.py
Normal file
202
taskflow/tests/unit/sql_db_api/test_logbook_api.py
Normal 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)
|
||||
190
taskflow/tests/unit/sql_db_api/test_taskdetail_api.py
Normal file
190
taskflow/tests/unit/sql_db_api/test_taskdetail_api.py
Normal 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)
|
||||
Reference in New Issue
Block a user