Merge pull request #20 from jessicalucci/sql
DB API / SQL Backend / Util files / Configs / Basic Celery
This commit is contained in:
2
setup.py
2
setup.py
@@ -10,7 +10,7 @@ def read_requires(base):
|
|||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
return requires
|
return requires
|
||||||
with open(path, 'rb') as h:
|
with open(path, 'rb') as h:
|
||||||
for line in h.read.splitlines():
|
for line in h.read().splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if len(line) == 0 or line.startswith("#"):
|
if len(line) == 0 or line.startswith("#"):
|
||||||
continue
|
continue
|
||||||
|
|||||||
17
taskflow/backends/celery/__init__.py
Normal file
17
taskflow/backends/celery/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (C) 2013 Rackspace Hosting Inc. 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.
|
||||||
51
taskflow/backends/celery/celeryapp.py
Normal file
51
taskflow/backends/celery/celeryapp.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (C) 2013 Rackspace Hosting Inc. 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 traceback as tb
|
||||||
|
|
||||||
|
from celery.signals import task_failure, task_success
|
||||||
|
from nova.openstack.common import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@task_failure.connect
|
||||||
|
def task_error_handler(signal=None, sender=None, task_id=None,
|
||||||
|
exception=None, args=None, kwargs=None,
|
||||||
|
traceback=None, einfo=None):
|
||||||
|
""" If a task errors out, log all error info """
|
||||||
|
LOG.error('Task %s, id: %s, called with args: %s, and kwargs: %s'
|
||||||
|
'failed with exception: %s' % (sender.name, task_id,
|
||||||
|
args, kwargs, exception))
|
||||||
|
LOG.error('Trackeback: %s' % (tb.print_tb(traceback), ))
|
||||||
|
wf = sender.name.split('.')[0]
|
||||||
|
task = ('.').join(n for n in (sender.name.split('.')[1:]) if n)
|
||||||
|
#logbook.update_task(wf, task, status="ERROR", args=args, kwargs=kwargs,
|
||||||
|
# exception=exception, traceback=(tb.print_tb(traceback)))
|
||||||
|
|
||||||
|
# TODO: Auto-initiate rollback from failed task
|
||||||
|
|
||||||
|
@task_success.connect
|
||||||
|
def task_success_handler(singal=None, sender=None, result=None):
|
||||||
|
""" Save task results to WF """
|
||||||
|
wf = sender.name.split('.')[0]
|
||||||
|
task = ('.').join(n for n in (sender.name.split('.')[1:]) if n)
|
||||||
|
#logbook.update_task(wf, task, status="SUCCESS", result=result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
31
taskflow/backends/celery/celeryconfig.py
Normal file
31
taskflow/backends/celery/celeryconfig.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (C) 2013 Rackspace Hosting Inc. 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.
|
||||||
|
|
||||||
|
""" Celery Configuration File """
|
||||||
|
|
||||||
|
from taskflow.common import config
|
||||||
|
|
||||||
|
config.register_celery_opts()
|
||||||
|
|
||||||
|
from oslo.cfg import cfg
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BROKER_URL = cfg.CFG('celery_mq')
|
||||||
|
CELERY_RESULT_BACKEND = "database"
|
||||||
|
CELERY_RESULT_DBURI = cfg.CFG('celery_backend')
|
||||||
19
taskflow/common/__init__.py
Normal file
19
taskflow/common/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
|
||||||
|
# Copyright (C) 2013 Rackspace Hosting Inc. 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.
|
||||||
|
|
||||||
50
taskflow/common/config.py
Normal file
50
taskflow/common/config.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
|
||||||
|
# Copyright (C) 2013 Rackspace Hosting Inc. 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Routines for configuring TaskFlow
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
db_opts = [
|
||||||
|
cfg.StrOpt('sql_connection',
|
||||||
|
default='mysql://task:flow@localhost/taskflow',
|
||||||
|
#default='sqlite:///test.db',
|
||||||
|
help='The SQLAlchemy connection string used to connect to the '
|
||||||
|
'database'),
|
||||||
|
cfg.IntOpt('sql_idle_timeout',
|
||||||
|
default=3600,
|
||||||
|
help='timeout before idle sql connections are reaped')]
|
||||||
|
|
||||||
|
celery_opts = [
|
||||||
|
cfg.StrOpt('celery_backend',
|
||||||
|
default='mysql://task:flow@localhost/taskflow',
|
||||||
|
help='The SQLAlchemy connection string used to connect to the '
|
||||||
|
'celery backend'),
|
||||||
|
cfg.StrOpt('celery_MQ',
|
||||||
|
default='mongodb://task:flow@localhost:27017/taskflow',
|
||||||
|
help='The MongoDB connection string used to connect to the '
|
||||||
|
'celery message queue')]
|
||||||
|
|
||||||
|
def register_db_opts():
|
||||||
|
cfg.CONF.register_opts(db_opts)
|
||||||
|
|
||||||
|
def register_celery_opts():
|
||||||
|
cfg.CONF.register_opts(celery_opts)
|
||||||
18
taskflow/db/__init__.py
Normal file
18
taskflow/db/__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.
|
||||||
131
taskflow/db/api.py
Normal file
131
taskflow/db/api.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# -*- 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"""
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from taskflow.common import config
|
||||||
|
from taskflow import utils
|
||||||
|
|
||||||
|
db_opts = [
|
||||||
|
cfg.StrOpt('db_backend',
|
||||||
|
default='sqlalchemy',
|
||||||
|
help='The backend to use for db')]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(db_opts)
|
||||||
|
|
||||||
|
IMPL = utils.LazyPluggable('db_backend',
|
||||||
|
sqlalchemy='taskflow.db.sqlalchemy.api')
|
||||||
|
|
||||||
|
def configure():
|
||||||
|
global SQL_CONNECTION
|
||||||
|
global SQL_IDLE_TIMEOUT
|
||||||
|
config.register_db_opts()
|
||||||
|
SQL_CONNECTION = cfg.CONF.sql_connection
|
||||||
|
SQL_IDLE_TIMEOUT = cfg.CONF.sql_idle_timeout
|
||||||
|
|
||||||
|
"""
|
||||||
|
LOGBOOK
|
||||||
|
"""
|
||||||
|
|
||||||
|
def logbook_get(context, lb_id):
|
||||||
|
return IMPL.logbook_get(context, lb_id)
|
||||||
|
|
||||||
|
def logbook_get_by_name(context, lb_name):
|
||||||
|
return IMPL.logbook_get_by_name(context, lb_name)
|
||||||
|
|
||||||
|
def logbook_create(context, lb_name, lb_id=None):
|
||||||
|
return IMPL.logbook_create(context, lb_name, lb_id)
|
||||||
|
|
||||||
|
def logbook_get_workflows(context, lb_id):
|
||||||
|
return IMPL.logbook_get_workflows(context, lb_id)
|
||||||
|
|
||||||
|
def logbook_add_workflow(context, lb_id, wf_name):
|
||||||
|
return IMPL.logbook_add_workflow(context, lb_id, wf_name)
|
||||||
|
|
||||||
|
def logbook_destroy(context, lb_id):
|
||||||
|
return IMPL.logbook_destroy(context, lb_id)
|
||||||
|
|
||||||
|
"""
|
||||||
|
JOB
|
||||||
|
"""
|
||||||
|
|
||||||
|
def job_get(context, job_id):
|
||||||
|
return IMPL.job_get(context, job_id)
|
||||||
|
|
||||||
|
def job_update(context, job_id, values):
|
||||||
|
return IMPL.job_update(context, job_id, values)
|
||||||
|
|
||||||
|
def job_add_workflow(context, job_id, wf_id):
|
||||||
|
return IMPL.job_add_workflow(context, job_id, wf_id)
|
||||||
|
|
||||||
|
def job_get_owner(context, job_id):
|
||||||
|
return IMPL.job_get_owner(context, job_id)
|
||||||
|
|
||||||
|
def job_get_state(context, job_id):
|
||||||
|
return IMPL.job_get_state(context, job_id)
|
||||||
|
|
||||||
|
def job_get_logbook(context, job_id):
|
||||||
|
return IMPL.job_get_logbook(context, job_id)
|
||||||
|
|
||||||
|
def job_destroy(context, job_id):
|
||||||
|
return IMPL.job_destroy(context, job_id)
|
||||||
|
|
||||||
|
"""
|
||||||
|
WORKFLOW
|
||||||
|
"""
|
||||||
|
|
||||||
|
def workflow_get(context, wf_name):
|
||||||
|
return IMPL.workflow_get(context, wf_name)
|
||||||
|
|
||||||
|
def workflow_get_all(context):
|
||||||
|
return IMPL.workflow_get_all(context)
|
||||||
|
|
||||||
|
def workflow_get_names(context):
|
||||||
|
return IMPL.workflow_get_names(context)
|
||||||
|
|
||||||
|
def workflow_get_tasks(context, wf_name):
|
||||||
|
return IMPL.workflow_get_tasks(context, wf_name)
|
||||||
|
|
||||||
|
def workflow_add_task(context, wf_name, task_id):
|
||||||
|
return IMPL.workflow_add_task(context, wf_name, task_id)
|
||||||
|
|
||||||
|
def workflow_create(context, wf_name):
|
||||||
|
return IMPL.workflow_create(context, wf_name)
|
||||||
|
|
||||||
|
def workflow_destroy(context, wf_name):
|
||||||
|
return IMPL.workflow_destroy(context, wf_name)
|
||||||
|
|
||||||
|
"""
|
||||||
|
TASK
|
||||||
|
"""
|
||||||
|
|
||||||
|
def task_get(context, task_id):
|
||||||
|
return IMPL.task_get(context, task_id)
|
||||||
|
|
||||||
|
def task_create(context, task_name, wf_id, task_id=None):
|
||||||
|
return IMPL.task_create(context, task_name, wf_id, task_id)
|
||||||
|
|
||||||
|
def task_update(context, task_id, values):
|
||||||
|
return IMPL.task_update(context, task_id, values)
|
||||||
|
|
||||||
|
def task_destroy(context, task_id):
|
||||||
|
return IMPL.task_destroy(context, task_id)
|
||||||
40
taskflow/db/base.py
Normal file
40
taskflow/db/base.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2010 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Base class for classes that need modular database access."""
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from taskflow.openstack.common import importutils
|
||||||
|
|
||||||
|
db_driver_opt = cfg.StrOpt('db_driver',
|
||||||
|
default='taskflow.db',
|
||||||
|
help='driver to use for database access')
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opt(db_driver_opt)
|
||||||
|
|
||||||
|
|
||||||
|
class Base(object):
|
||||||
|
"""DB driver is injected in the init method."""
|
||||||
|
|
||||||
|
def __init__(self, db_driver=None):
|
||||||
|
if not db_driver:
|
||||||
|
db_driver = CONF.db_driver
|
||||||
|
self.db = importutils.import_module(db_driver) # pylint: disable=C0103
|
||||||
|
|
||||||
18
taskflow/db/migration.py
Normal file
18
taskflow/db/migration.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.
|
||||||
18
taskflow/db/sqlalchemy/__init__.py
Normal file
18
taskflow/db/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.
|
||||||
251
taskflow/db/sqlalchemy/api.py
Normal file
251
taskflow/db/sqlalchemy/api.py
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# -*- 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 taskflow.db.sqlalchemy import models
|
||||||
|
from taskflow.db.sqlalchemy.session import get_session
|
||||||
|
|
||||||
|
from taskflow.openstack.common import exception
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def model_query(context, *args, **kwargs):
|
||||||
|
session = kwargs.get('session') or get_session()
|
||||||
|
query = session.query(*args)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
"""
|
||||||
|
LOGBOOK
|
||||||
|
"""
|
||||||
|
|
||||||
|
def logbook_get(context, lb_id, session=None):
|
||||||
|
"""Return a logbook with matching lb_id"""
|
||||||
|
query = model_query(context, models.LogBook, session=session).\
|
||||||
|
filter_by(logbook_id=lb_id)
|
||||||
|
|
||||||
|
if not query:
|
||||||
|
raise exception.NotFound("No LogBook found with id "
|
||||||
|
"%s." % (lb_id,))
|
||||||
|
|
||||||
|
return query.first()
|
||||||
|
|
||||||
|
def logbook_get_by_name(context, lb_name):
|
||||||
|
"""Return all logbooks with matching name"""
|
||||||
|
query = model_query(context, models.LogBook).\
|
||||||
|
filter_by(name=lb_name)
|
||||||
|
|
||||||
|
if not query:
|
||||||
|
raise exception.NotFound("LogBook %s not found."
|
||||||
|
% (lb_name,))
|
||||||
|
|
||||||
|
return query.all()
|
||||||
|
|
||||||
|
def logbook_create(context, name, lb_id=None):
|
||||||
|
"""Create a new logbook"""
|
||||||
|
lb_ref = models.LogBook()
|
||||||
|
lb_ref.name = name
|
||||||
|
if lb_id:
|
||||||
|
lb_ref.logbook_id = lb_id
|
||||||
|
lb_ref.save()
|
||||||
|
|
||||||
|
return lb_ref
|
||||||
|
|
||||||
|
def logbook_get_workflows(context, lb_id):
|
||||||
|
"""Return all workflows associated with a logbook"""
|
||||||
|
lb = logbook_get(context, lb_id)
|
||||||
|
|
||||||
|
return lb.workflows
|
||||||
|
|
||||||
|
def logbook_add_workflow(context, lb_id, wf_name):
|
||||||
|
"""Add Workflow to given LogBook"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
wf = workflow_get(context, wf_name, session=session)
|
||||||
|
lb = logbook_get(context, lb_id, session=session)
|
||||||
|
|
||||||
|
lb.workflows.append(wf)
|
||||||
|
|
||||||
|
return lb.workflows
|
||||||
|
|
||||||
|
def logbook_destroy(context, lb_id):
|
||||||
|
"""Delete a given LogBook"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
lb = logbook_get(context, lb_id, session=session)
|
||||||
|
lb.delete()
|
||||||
|
|
||||||
|
"""
|
||||||
|
JOB
|
||||||
|
"""
|
||||||
|
|
||||||
|
def job_get(context, job_id, session=None):
|
||||||
|
"""Return Job with matching job_id"""
|
||||||
|
query = model_query(context, models.Workflow, session=session).\
|
||||||
|
filter_by(job_id=job_id)
|
||||||
|
|
||||||
|
if not query:
|
||||||
|
raise exception.NotFound("No Job with id %s found"
|
||||||
|
% (job_id,))
|
||||||
|
|
||||||
|
return query.first()
|
||||||
|
|
||||||
|
def job_update(context, job_id, values):
|
||||||
|
"""Update job with given values"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
job = job_get(context, job_id, session=session)
|
||||||
|
job.update(values)
|
||||||
|
job.save(session=session)
|
||||||
|
|
||||||
|
def job_add_workflow(context, job_id, wf_id):
|
||||||
|
"""Add a Workflow to given job"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
job = job_get(context, job_id)
|
||||||
|
wf = workflow_get(context, wf_id)
|
||||||
|
job.workflows.append(wf)
|
||||||
|
return job.workflows
|
||||||
|
|
||||||
|
def job_get_owner(context, job_id):
|
||||||
|
"""Return a job's current owner"""
|
||||||
|
job = job_get(context, job_id)
|
||||||
|
return job.owner
|
||||||
|
|
||||||
|
def job_get_state(context, job_id):
|
||||||
|
"""Return a job's current owner"""
|
||||||
|
job = job_get(context, job_id)
|
||||||
|
return job.state
|
||||||
|
|
||||||
|
def job_get_logbook(context, job_id):
|
||||||
|
"""Return the logbook associated with the given job"""
|
||||||
|
job = job_get(context, job_id)
|
||||||
|
return job.logbook
|
||||||
|
|
||||||
|
def job_destroy(context, job_id):
|
||||||
|
"""Delete a given Job"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
job = job_get(context, job_id, session=session)
|
||||||
|
job.delete()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
WORKFLOW
|
||||||
|
"""
|
||||||
|
|
||||||
|
def workflow_get(context, wf_name, session=None):
|
||||||
|
"""Return one workflow with matching workflow_id"""
|
||||||
|
query = model_query(context, models.Workflow, session=session).\
|
||||||
|
filter_by(name=wf_name)
|
||||||
|
|
||||||
|
if not query:
|
||||||
|
raise exception.NotFound("Workflow %s not found." % (wf_name,))
|
||||||
|
|
||||||
|
return query.first()
|
||||||
|
|
||||||
|
def workflow_get_all(context):
|
||||||
|
"""Return all workflows"""
|
||||||
|
results = model_query(context, models.Workflow).all()
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
raise exception.NotFound("No Workflows were found.")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def workflow_get_names(context):
|
||||||
|
"""Return all workflow names"""
|
||||||
|
results = model_query(context, models.Workflow.name).all()
|
||||||
|
|
||||||
|
return zip(*results)
|
||||||
|
|
||||||
|
def workflow_get_tasks(context, wf_name):
|
||||||
|
"""Return all tasks for a given Workflow"""
|
||||||
|
wf = workflow_get(context, wf_name)
|
||||||
|
|
||||||
|
return wf.tasks
|
||||||
|
|
||||||
|
def workflow_add_task(context, wf_id, task_id):
|
||||||
|
"""Add a task to a given workflow"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
task = task_get(context, task_id, session=session)
|
||||||
|
wf = workflow_get(context, wf_id, session=session)
|
||||||
|
wf.tasks.append(task)
|
||||||
|
return wf.tasks
|
||||||
|
|
||||||
|
def workflow_create(context, workflow_name):
|
||||||
|
"""Create new workflow with workflow_id"""
|
||||||
|
workflow_ref = models.Workflow()
|
||||||
|
workflow_ref.name = workflow_name
|
||||||
|
workflow_ref.save()
|
||||||
|
|
||||||
|
return workflow_ref
|
||||||
|
|
||||||
|
def workflow_destroy(context, wf_name):
|
||||||
|
"""Delete a given Workflow"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
wf = workflow_get(context, wf_name, session=session)
|
||||||
|
wf.delete()
|
||||||
|
|
||||||
|
"""
|
||||||
|
TASK
|
||||||
|
"""
|
||||||
|
|
||||||
|
def task_get(context, task_id, session=None):
|
||||||
|
"""Return Task with task_id"""
|
||||||
|
result = model_query(context, models.Task, session=session).\
|
||||||
|
filter_by(task_id=task_id)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise exception.NotFound("No Task found with id "
|
||||||
|
"%s." % (task_id,))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def task_create(context, task_name, wf_id, task_id=None):
|
||||||
|
"""Create task associated with given workflow"""
|
||||||
|
task_ref = models.Task()
|
||||||
|
task_ref.name = task_name
|
||||||
|
task_ref.wf_id = wf_id
|
||||||
|
if task_id:
|
||||||
|
task_ref.task_id = task_id
|
||||||
|
task_ref.save()
|
||||||
|
|
||||||
|
return task_ref
|
||||||
|
|
||||||
|
def task_update(context, task_id, values):
|
||||||
|
"""Update Task with given values"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
task = task_get(context, task_id)
|
||||||
|
|
||||||
|
task.update(values)
|
||||||
|
task.save(session=session)
|
||||||
|
|
||||||
|
def task_destroy(context, task_id):
|
||||||
|
"""Delete an existing Task"""
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
task = task_get(context, task_id, session=session)
|
||||||
|
task.delete()
|
||||||
176
taskflow/db/sqlalchemy/models.py
Normal file
176
taskflow/db/sqlalchemy/models.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# -*- 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Table, MetaData
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import object_mapper, relationship, backref
|
||||||
|
from sqlalchemy import DateTime, ForeignKey
|
||||||
|
from sqlalchemy import types as types
|
||||||
|
|
||||||
|
from json import dumps, loads
|
||||||
|
|
||||||
|
from taskflow.db.sqlalchemy.session import get_session, get_engine
|
||||||
|
from taskflow.openstack.common import timeutils, uuidutils
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
BASE = declarative_base()
|
||||||
|
|
||||||
|
class Json(types.TypeDecorator, types.MutableType):
|
||||||
|
impl = types.Text
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
return dumps(value)
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect):
|
||||||
|
return 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 = 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 = 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()
|
||||||
|
|
||||||
|
workflow_logbook_assoc = Table('wf_lb_assoc', BASE.metadata,
|
||||||
|
Column('workflow_id', Integer, ForeignKey('workflow.id')),
|
||||||
|
Column('logbook_id', Integer, ForeignKey('logbook.id')),
|
||||||
|
Column('id', Integer, primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
workflow_job_assoc = Table('wf_job_assoc', BASE.metadata,
|
||||||
|
Column('workflow_id', Integer, ForeignKey('workflow.id')),
|
||||||
|
Column('job_id', Integer, ForeignKey('job.id')),
|
||||||
|
Column('id', Integer, primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
class LogBook(BASE, TaskFlowBase):
|
||||||
|
"""Represents a logbook for a set of workflows"""
|
||||||
|
|
||||||
|
__tablename__ = 'logbook'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
logbook_id = Column(String, default=uuidutils.generate_uuid,
|
||||||
|
unique=True)
|
||||||
|
name = Column(String)
|
||||||
|
workflows = relationship("Workflow",
|
||||||
|
secondary=workflow_logbook_assoc)
|
||||||
|
job = relationship("Job", uselist=False, backref="logbook")
|
||||||
|
|
||||||
|
class Job(BASE, TaskFlowBase):
|
||||||
|
"""Represents a Job"""
|
||||||
|
|
||||||
|
__tablename__ = 'job'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
job_id = Column(String, default=uuidutils.generate_uuid,
|
||||||
|
unique=True)
|
||||||
|
name = Column(String)
|
||||||
|
owner = Column(String)
|
||||||
|
state = Column(String)
|
||||||
|
workflows = relationship("Workflow",
|
||||||
|
secondary=workflow_job_assoc)
|
||||||
|
logbook_id = Column(String, ForeignKey('logbook.logbook_id')
|
||||||
|
|
||||||
|
|
||||||
|
class Workflow(BASE, TaskFlowBase):
|
||||||
|
"""Represents Workflow detail objects"""
|
||||||
|
|
||||||
|
__tablename__ = 'workflow'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String, unique=True)
|
||||||
|
tasks = relationship("Task", backref="workflow")
|
||||||
|
|
||||||
|
class Task(BASE, TaskFlowBase):
|
||||||
|
"""Represents Task detail objects"""
|
||||||
|
|
||||||
|
__tablename__ = 'task'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
task_id = Column(String, default=uuidutils.generate_uuid)
|
||||||
|
name = Column(String)
|
||||||
|
results = Column(Json)
|
||||||
|
exception = Column(String)
|
||||||
|
stacktrace = Column(String)
|
||||||
|
workflow_id = Column(String, ForeignKey('workflow.id'))
|
||||||
|
|
||||||
|
def create_tables():
|
||||||
|
BASE.metadata.create_all(get_engine())
|
||||||
109
taskflow/db/sqlalchemy/session.py
Normal file
109
taskflow/db/sqlalchemy/session.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# -*- 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.orm
|
||||||
|
import sqlalchemy.engine
|
||||||
|
import sqlalchemy.interfaces
|
||||||
|
import sqlalchemy
|
||||||
|
from sqlalchemy.pool import NullPool
|
||||||
|
|
||||||
|
from taskflow.db import api as db_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 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: 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 db_api.SQL_CONNECTION
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sql_idle_timeout():
|
||||||
|
return db_api.SQL_IDLE_TIMEOUT
|
||||||
31
taskflow/openstack/common/exception.py
Normal file
31
taskflow/openstack/common/exception.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Exceptions common to OpenStack projects
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
def __init__(self, message=None):
|
||||||
|
super(Error, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class NotFound(Error):
|
||||||
|
pass
|
||||||
68
taskflow/openstack/common/importutils.py
Normal file
68
taskflow/openstack/common/importutils.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack Foundation.
|
||||||
|
# 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 related utilities and helper functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
def import_class(import_str):
|
||||||
|
"""Returns a class from a string including module and class"""
|
||||||
|
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||||
|
try:
|
||||||
|
__import__(mod_str)
|
||||||
|
return getattr(sys.modules[mod_str], class_str)
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
raise ImportError('Class %s cannot be found (%s)' %
|
||||||
|
(class_str,
|
||||||
|
traceback.format_exception(*sys.exc_info())))
|
||||||
|
|
||||||
|
|
||||||
|
def import_object(import_str, *args, **kwargs):
|
||||||
|
"""Import a class and return an instance of it."""
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_object_ns(name_space, import_str, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Import a class and return an instance of it, first by trying
|
||||||
|
to find the class in a default namespace, then failing back to
|
||||||
|
a full path if not found in the default namespace.
|
||||||
|
"""
|
||||||
|
import_value = "%s.%s" % (name_space, import_str)
|
||||||
|
try:
|
||||||
|
return import_class(import_value)(*args, **kwargs)
|
||||||
|
except ImportError:
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_module(import_str):
|
||||||
|
"""Import a module."""
|
||||||
|
__import__(import_str)
|
||||||
|
return sys.modules[import_str]
|
||||||
|
|
||||||
|
|
||||||
|
def try_import(import_str, default=None):
|
||||||
|
"""Try to import a module and if it fails return default."""
|
||||||
|
try:
|
||||||
|
return import_module(import_str)
|
||||||
|
except ImportError:
|
||||||
|
return default
|
||||||
|
|
||||||
33
taskflow/openstack/common/timeutils.py
Normal file
33
taskflow/openstack/common/timeutils.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Time related utilities and helper functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
def utcnow():
|
||||||
|
"""Overridable version of utils.utcnow."""
|
||||||
|
if utcnow.override_time:
|
||||||
|
try:
|
||||||
|
return utcnow.override_time.pop(0)
|
||||||
|
except AttributeError:
|
||||||
|
return utcnow.override_time
|
||||||
|
return datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
utcnow.override_time = None
|
||||||
27
taskflow/openstack/common/uuidutils.py
Normal file
27
taskflow/openstack/common/uuidutils.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2012 Intel Corporation.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
UUID related utilities and helper functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def generate_uuid():
|
||||||
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
87
taskflow/patterns/distributed_flow.py
Normal file
87
taskflow/patterns/distributed_flow.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
|
||||||
|
# Copyright (C) 2013 Rackspace Hosting Inc. 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 logging
|
||||||
|
|
||||||
|
from taskflow import logbook
|
||||||
|
|
||||||
|
from celery import chord
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Flow(object):
|
||||||
|
"""A linear chain of independent tasks that can be applied as one unit or
|
||||||
|
rolled back as one unit."""
|
||||||
|
|
||||||
|
def __init__(self, name, tolerant=False, parents=None):
|
||||||
|
self.name = name
|
||||||
|
self.root = None
|
||||||
|
self._tasks = []
|
||||||
|
logbook.add_workflow(name)
|
||||||
|
|
||||||
|
|
||||||
|
def chain_listeners(self, context, initial_task, callback_task):
|
||||||
|
""" Register one listener for a task """
|
||||||
|
if self.root is None:
|
||||||
|
initial_task.name = '%s.%s' % (self.name, initial_task.name)
|
||||||
|
self.root = initial_task.s(context)
|
||||||
|
self._tasks.append(initial_task)
|
||||||
|
LOG.info('WF %s root task set to %s' % (self.name, initial_task.name))
|
||||||
|
|
||||||
|
callback_task.name = '%s.%s' % (self.name, callback_task.name)
|
||||||
|
self._tasks.append(callback_task)
|
||||||
|
|
||||||
|
initial_task.link(callback_task.s(context))
|
||||||
|
|
||||||
|
def split_listeners(self, context, initial_task, callback_tasks):
|
||||||
|
""" Register multiple listeners for one task """
|
||||||
|
if self.root is None:
|
||||||
|
initial_task.name = '%s.%s' % (self.name, initial_task.name)
|
||||||
|
self.root = initial_task.s(context)
|
||||||
|
self._tasks.append(initial_task)
|
||||||
|
LOG.info('WF %s root task set to %s' % (self.name, initial_task.name))
|
||||||
|
for task in callback_tasks:
|
||||||
|
task.name = '%s.%s' % (self.name, task.name)
|
||||||
|
self._tasks.append(task)
|
||||||
|
initial_task.link(task.s(context))
|
||||||
|
|
||||||
|
def merge_listeners(self, context, inital_tasks, callback_task):
|
||||||
|
""" Register one listener for multiple tasks """
|
||||||
|
header = []
|
||||||
|
if self.root is None:
|
||||||
|
self.root = []
|
||||||
|
for task in initial_tasks:
|
||||||
|
task.name = '%s.%s' % (self.name, task.name)
|
||||||
|
self._tasks.append(task)
|
||||||
|
header.append(task.s(context))
|
||||||
|
if isinstance(self.root, list):
|
||||||
|
self.root.append(task.s(context))
|
||||||
|
LOG.info('WF %s added root task %s' %
|
||||||
|
(self.name, task.name))
|
||||||
|
callback_task.name = '%s.%s' % (self.name, callback_task.name)
|
||||||
|
self._tasks.append(callback_task)
|
||||||
|
|
||||||
|
#TODO: Need to set up chord so that it's not executed immediately
|
||||||
|
c = chord(header, body=callback_task)
|
||||||
|
|
||||||
|
def run(self, context, *args, **kwargs):
|
||||||
|
""" Start root task and kick off workflow """
|
||||||
|
self.root(context)
|
||||||
|
LOG.info('WF %s has been started' % (self.name,))
|
||||||
@@ -113,3 +113,30 @@ class ReaderWriterLock(object):
|
|||||||
self.readers_ok.acquire()
|
self.readers_ok.acquire()
|
||||||
self.readers_ok.notifyAll()
|
self.readers_ok.notifyAll()
|
||||||
self.readers_ok.release()
|
self.readers_ok.release()
|
||||||
|
|
||||||
|
class LazyPluggable(object):
|
||||||
|
"""A pluggable backend loaded lazily based on some value."""
|
||||||
|
|
||||||
|
def __init__(self, pivot, **backends):
|
||||||
|
self.__backends = backends
|
||||||
|
self.__pivot = pivot
|
||||||
|
self.__backend = None
|
||||||
|
|
||||||
|
def __get_backend(self):
|
||||||
|
if not self.__backend:
|
||||||
|
backend_name = 'sqlalchemy'
|
||||||
|
backend = self.__backends[backend_name]
|
||||||
|
if isinstance(backend, tuple):
|
||||||
|
name = backend[0]
|
||||||
|
fromlist = backend[1]
|
||||||
|
else:
|
||||||
|
name = backend
|
||||||
|
fromlist = backend
|
||||||
|
|
||||||
|
self.__backend = __import__(name, None, None, fromlist)
|
||||||
|
return self.__backend
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
backend = self.__get_backend()
|
||||||
|
return getattr(backend, key)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user