Support workflow creation

Partial-Implements: blueprint workflow

Change-Id: I19f4a82b373cbc6f4c4367648ec1c66a35ce4e2e
This commit is contained in:
Liuqing Jing 2015-11-19 17:38:21 +08:00 committed by lawrancejing
parent ed1295154d
commit bf30391841
15 changed files with 229 additions and 0 deletions

View File

@ -13,6 +13,7 @@
import pecan
from evoque.api.controllers.v1 import ticket
from evoque.api.controllers.v1 import workflow
class Controller(object):
@ -20,6 +21,7 @@ class Controller(object):
def __init__(self):
self.sub_controllers = {
"ticket": ticket.Controller(),
"workflow": workflow.Controller(),
}
for name, ctrl in self.sub_controllers.items():

View File

@ -0,0 +1,28 @@
# 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 pecan
from pecan import rest
class Controller(rest.RestController):
@pecan.expose('json')
def get(self):
return {"version": "1.0.0"}
@pecan.expose('json')
def post(self, **kwargs):
workflow = pecan.request.workflow_api.workflow_create(
name=kwargs['name'], wf_spec=kwargs['wf_spec'])
return workflow

View File

@ -16,6 +16,7 @@ from oslo_config import cfg
from evoque.common import context
from evoque.engine.ticket import api as ticket_api
from evoque.engine.workflow import api as workflow_api
CONF = cfg.CONF
CONF.import_opt('auth_uri', 'keystonemiddleware.auth_token',
@ -79,3 +80,5 @@ class RPCHook(hooks.PecanHook):
def before(self, state):
state.request.ticket_api = ticket_api.API(
context=state.request.context)
state.request.workflow_api = workflow_api.API(
context=state.request.context)

View File

@ -23,6 +23,7 @@ from evoque.common.i18n import _LI
from evoque.common import rpc_service
from evoque.common import service as evoque_service
from evoque.engine.ticket import endpoint as ticket_endpoint
from evoque.engine.workflow import endpoint as workflow_endpoint
LOG = logging.getLogger(__name__)
@ -36,6 +37,7 @@ def main():
endpoints = [
ticket_endpoint.Handler(),
workflow_endpoint.Handler(),
]
server = rpc_service.Service.create("evoque-engine",

View File

@ -45,6 +45,11 @@ def ticket_get_all(context, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None)
# Workflows
def workflow_create(context, values):
return IMPL.workflow_create(context, values)
# Utils
def db_sync(engine, version=None):
"""Migrate the database to `version` or the most recent version."""

View File

@ -89,6 +89,14 @@ def ticket_get_all(context, filters=None, limit=None, marker=None,
sort_key, sort_dir, query)
# Workflows
def workflow_create(context, values):
workflow_ref = models.Workflow()
workflow_ref.update(values)
workflow_ref.save(_session())
return workflow_ref
# Utils
def db_sync(engine, version=None):
"""Migrate the database to `version` or the most recent version."""

View File

@ -40,8 +40,29 @@ def upgrade(migrate_engine):
mysql_charset='utf8'
)
workflow = sqlalchemy.Table(
'workflow', meta,
sqlalchemy.Column('id', sqlalchemy.String(36),
primary_key=True, nullable=False),
sqlalchemy.Column('name', sqlalchemy.String(255)),
sqlalchemy.Column('spec', types.MediumText()),
sqlalchemy.Column('user', sqlalchemy.String(32)),
sqlalchemy.Column('project', sqlalchemy.String(32)),
sqlalchemy.Column('domain', sqlalchemy.String(32)),
sqlalchemy.Column('user_id', sqlalchemy.String(255)),
sqlalchemy.Column('project_id', sqlalchemy.String(255)),
sqlalchemy.Column('domain_id', sqlalchemy.String(255)),
sqlalchemy.Column('metadata', types.Dict),
sqlalchemy.Column('created_at', sqlalchemy.DateTime),
sqlalchemy.Column('updated_at', sqlalchemy.DateTime),
sqlalchemy.Column('deleted_at', sqlalchemy.DateTime),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
tables = (
ticket,
workflow
)
for index, table in enumerate(tables):

View File

@ -51,3 +51,21 @@ class Ticket(BASE, EvoqueBase):
user_id = sqlalchemy.Column(sqlalchemy.String(255))
project_id = sqlalchemy.Column(sqlalchemy.String(255))
domain_id = sqlalchemy.Column(sqlalchemy.String(255))
class Workflow(BASE, EvoqueBase):
"""Represents a workflow created by the Evoque engine."""
__tablename__ = 'workflow'
id = sqlalchemy.Column('id', sqlalchemy.String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
name = sqlalchemy.Column('name', sqlalchemy.String(255))
spec = sqlalchemy.Column('spec', types.MediumText())
user = sqlalchemy.Column(sqlalchemy.String(32))
project = sqlalchemy.Column(sqlalchemy.String(32))
domain = sqlalchemy.Column(sqlalchemy.String(32))
user_id = sqlalchemy.Column(sqlalchemy.String(255))
project_id = sqlalchemy.Column(sqlalchemy.String(255))
domain_id = sqlalchemy.Column(sqlalchemy.String(255))

View File

@ -13,6 +13,7 @@
import json
from sqlalchemy.dialects import mysql
from sqlalchemy import Text
from sqlalchemy import types
@ -32,3 +33,7 @@ class Dict(types.TypeDecorator):
if value is None:
return None
return json.loads(value)
def MediumText():
return Text().with_variant(mysql.MEDIUMTEXT(), 'mysql')

View File

@ -0,0 +1,51 @@
# 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 abc
import six
from oslo_config import cfg
from stevedore import driver
from evoque.common.i18n import _
OPTS = [
cfg.StrOpt('workflow_engine',
default='spiff',
help=_('The Evoque workflow engine driver.')),
]
CONF = cfg.CONF
CONF.register_opts(OPTS)
def get_workflow(engine=CONF.workflow_engine, namespace='evoque.workflow'):
"""Get workflow driver and load it.
:param engine: workflow engine
:param namespace: Namespace to use to look for drivers.
"""
loaded_driver = driver.DriverManager(namespace, engine)
return loaded_driver.driver()
@six.add_metaclass(abc.ABCMeta)
class Base(object):
"""Base class for workflow operations."""
def __init__(self):
super(Base, self).__init__()
@abc.abstractmethod
def workflow_create(self, wf_spec):
"""Create a workflow"""

View File

@ -0,0 +1,22 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from evoque.common import rpc_service
class API(rpc_service.API):
def __init__(self, transport=None, context=None, topic=None):
super(API, self).__init__(transport, context,
topic="evoque-engine")
def workflow_create(self, name, wf_spec):
return self._call('workflow_create', name=name, wf_spec=wf_spec)

View File

@ -0,0 +1,25 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from evoque.engine import workflow
class Handler(object):
def __init__(self):
super(Handler, self).__init__()
self.workflow_driver = workflow.get_workflow()
def workflow_create(self, context, name, wf_spec):
workflow = self.workflow_driver.workflow_create(
context, name, wf_spec)
return {'workflow': workflow}

View File

@ -0,0 +1,33 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from evoque.db import api as db_api
from evoque.engine import workflow
class Workflow(workflow.Base):
"""Workflow engine powered by `SpiffWorkflow`
https://github.com/knipknap/SpiffWorkflow
"""
def __init__(self):
super(Workflow, self).__init__()
def workflow_create(self, context, name, wf_spec):
"""Create a workflow"""
values = {
'name': name,
'spec': wf_spec
}
workflow = db_api.workflow_create(context, values)
return workflow

View File

@ -52,5 +52,8 @@ def list_opts():
cfg.StrOpt('host',
default=socket.getfqdn(),
help=_('The listen IP for the Evoque engine server.')),
cfg.StrOpt('workflow_engine',
default='spiff',
help=_('The Evoque workflow engine driver.')),
)),
]

View File

@ -29,6 +29,9 @@ console_scripts =
evoque-manage = evoque.cmd.manage:main
evoque-engine = evoque.cmd.engine:main
evoque.workflow =
spiff = evoque.engine.workflow.spiff:Workflow
oslo.config.opts =
evoque = evoque.opts:list_opts