diff --git a/requirements.txt b/requirements.txt index 238f9fb..514d24c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +git+https://github.com/stackforge/python-mistralclient.git pbr>=0.5.21,<1.0 eventlet>=0.13.0 pyyaml diff --git a/mistralextra/__init__.py b/simple_app/__init__.py similarity index 100% rename from mistralextra/__init__.py rename to simple_app/__init__.py diff --git a/mistralextra/examples/__init__.py b/simple_app/api/__init__.py similarity index 100% rename from mistralextra/examples/__init__.py rename to simple_app/api/__init__.py diff --git a/simple_app/api/app.py b/simple_app/api/app.py new file mode 100644 index 0000000..4064aa1 --- /dev/null +++ b/simple_app/api/app.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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 + + +app = { + 'root': 'demo_app.api.controllers.root.RootController', + 'modules': ['demo_app.api'], + 'debug': True, +} + + +def get_pecan_config(): + # Set up the pecan configuration + return pecan.configuration.conf_from_dict(app) + + +def setup_app(config=None): + if not config: + config = get_pecan_config() + + app_conf = dict(config) + + return pecan.make_app( + app_conf.pop('root'), + logging=getattr(config, 'logging', {}), + **app_conf + ) diff --git a/simple_app/api/client.py b/simple_app/api/client.py new file mode 100644 index 0000000..4e5940e --- /dev/null +++ b/simple_app/api/client.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +import pkg_resources as pkg + +from mistralclient.api import client + +from simple_app import version + +MISTRAL_URL = "http://localhost:8989/v1" +client.Client.authenticate = mock.MagicMock(return_value=(MISTRAL_URL, + "", "", "")) +CLIENT = client.Client(mistral_url=MISTRAL_URL, + project_name="mistral_demo") + + +WB_NAME = "myWorkbook" +TARGET_TASK = "task4" + + +def upload_workbook(): + try: + CLIENT.workbooks.get(WB_NAME) + except: + CLIENT.workbooks.create(WB_NAME, + description="My test workbook", + tags=["test"]) + print("Uploading workbook definition...\n") + definition = get_workbook_definition() + CLIENT.workbooks.upload_definition(WB_NAME, definition) + print definition + print("\nUploaded.") + + +def get_workbook_definition(): + return open(pkg.resource_filename(version.version_info.package, + "demo.yaml")).read() + + +def start_execution(): + import threading + t = threading.Thread(target=CLIENT.executions.create, + kwargs={'workbook_name': WB_NAME, + 'target_task': TARGET_TASK}) + t.start() + return "accepted" diff --git a/simple_app/api/controllers/__init__.py b/simple_app/api/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simple_app/api/controllers/resource.py b/simple_app/api/controllers/resource.py new file mode 100644 index 0000000..2ee8395 --- /dev/null +++ b/simple_app/api/controllers/resource.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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 wsme import types as wtypes + + +LOG = logging.getLogger(__name__) + +API_STATUS = wtypes.Enum(str, 'SUPPORTED', 'CURRENT', 'DEPRECATED') + + +class Resource(wtypes.Base): + """REST API Resource.""" + + @classmethod + def from_dict(cls, d): + # TODO: take care of nested resources + obj = cls() + + for key, val in d.items(): + if hasattr(obj, key): + setattr(obj, key, val) + + return obj + + def __str__(self): + """WSME based implementation of __str__.""" + + res = "%s [" % type(self).__name__ + + first = True + for attr in self._wsme_attributes: + if not first: + res += ', ' + else: + first = False + + res += "%s='%s'" % (attr.name, getattr(self, attr.name)) + + return res + "]" diff --git a/simple_app/api/controllers/root.py b/simple_app/api/controllers/root.py new file mode 100644 index 0000000..111f0af --- /dev/null +++ b/simple_app/api/controllers/root.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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 +import pecan +from pecan import rest +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from simple_app.api.controllers import resource +from simple_app.api.controllers import tasks +from simple_app.api import client + +LOG = logging.getLogger(__name__) + +API_STATUS = wtypes.Enum(str, 'SUPPORTED', 'CURRENT', 'DEPRECATED') + + +class Link(resource.Resource): + """Web link.""" + + href = wtypes.text + target = wtypes.text + + +class APIVersion(resource.Resource): + """API Version.""" + + id = wtypes.text + status = API_STATUS + link = Link + + +class StartController(rest.RestController): + @wsme_pecan.wsexpose(wtypes.text) + def get(self): + print("Start execution for: %s" % client.TARGET_TASK) + + return client.start_execution() + + +class RootController(object): + + tasks = tasks.Controller() + start = StartController() + + @wsme_pecan.wsexpose([APIVersion]) + def index(self): + LOG.debug("Fetching API versions.") + + host_url = '%s/%s' % (pecan.request.host_url, 'v1') + api_v1 = APIVersion(id='v1.0', + status='CURRENT', + link=Link(href=host_url, target='v1')) + + return [api_v1] diff --git a/simple_app/api/controllers/tasks.py b/simple_app/api/controllers/tasks.py new file mode 100644 index 0000000..c8a6a27 --- /dev/null +++ b/simple_app/api/controllers/tasks.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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 pecan import abort +from pecan import rest +import pecan +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from simple_app import tasks + + +LOG = logging.getLogger(__name__) + + +class Controller(rest.RestController): + """API root controller""" + + @wsme_pecan.wsexpose(wtypes.text) + def get_all(self): + LOG.debug("Fetch items.") + + values = { + 'tasks': [ + 'task1', + 'task2', + 'task3', + 'task4' + ] + } + + if not values: + abort(404) + else: + return values + + @wsme_pecan.wsexpose(wtypes.text, wtypes.text) + def get(self, name): + print("Task '%s' is starting" % name) + + value = "Task %s accepted" % name + tasks.start_task(**self.get_mistral_headers()) + return value + + def get_mistral_headers(self): + headers = pecan.request.headers + try: + needed_headers = { + 'workbook_name': headers['Mistral-Workbook-Name'], + 'execution_id': headers['Mistral-Execution-Id'], + 'task_id': headers['Mistral-Task-Id'] + } + return needed_headers + except KeyError: + raise RuntimeError("Could not find http headers for " + "defining mistral task") diff --git a/simple_app/cmd/__init__.py b/simple_app/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simple_app/cmd/main.py b/simple_app/cmd/main.py new file mode 100644 index 0000000..0908292 --- /dev/null +++ b/simple_app/cmd/main.py @@ -0,0 +1,80 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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. + +"""Script to start Demo API service.""" + +import eventlet + +import logging +import os +from requests import exceptions +import sys +import threading +from time import sleep +from wsgiref import simple_server + +from oslo.config import cfg + +from simple_app import config +from simple_app.api import app +from simple_app.api import client + + +eventlet.monkey_patch( + os=True, select=True, socket=True, thread=True, time=True) + +logging.basicConfig(level=logging.WARN) +LOG = logging.getLogger(__name__) +CLIENT = client.CLIENT + + +def upload_wb_and_start(): + sleep(5) + + try: + client.upload_workbook() + except exceptions.ConnectionError: + LOG.error("Error. Mistral service probably is not working now") + sys.exit(1) + + print("Start execution for: %s" % client.TARGET_TASK) + + client.start_execution() + + +def main(): + try: + config.parse_args() + + host = cfg.CONF.api.host + port = cfg.CONF.api.port + + server = simple_server.make_server(host, port, app.setup_app()) + + LOG.info("Demo app API is serving on http://%s:%s (PID=%s)" % + (host, port, os.getpid())) + + server.serve_forever() + except RuntimeError, e: + sys.stderr.write("ERROR: %s\n" % e) + sys.exit(1) + + +if __name__ == '__main__': + upload_thread = threading.Thread(target=upload_wb_and_start) + upload_thread.run() + main() diff --git a/simple_app/config.py b/simple_app/config.py new file mode 100644 index 0000000..0a5f72a --- /dev/null +++ b/simple_app/config.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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 oslo.config import cfg + +from simple_app import version + + +api_opts = [ + cfg.StrOpt('host', default='0.0.0.0', help='Simple-app API server host'), + cfg.IntOpt('port', default=8988, help='Simple-app API server port') +] + +CONF = cfg.CONF +CONF.register_opts(api_opts, group='api') + + +def parse_args(args=None, usage=None, default_config_files=None): + CONF(args=args, + project='mistral-demo', + version=version, + usage=usage, + default_config_files=default_config_files) diff --git a/simple_app/demo.yaml b/simple_app/demo.yaml new file mode 100644 index 0000000..d89f663 --- /dev/null +++ b/simple_app/demo.yaml @@ -0,0 +1,57 @@ +Services: + MyRest: + type: REST_API + parameters: + baseUrl: http://localhost:8988 + actions: + task1: + parameters: + url: /tasks/task1 + method: GET + task-parameters: + + task2: + parameters: + url: /tasks/task2 + method: GET + task-parameters: + + task3: + parameters: + url: /tasks/task3 + method: GET + task-parameters: + + task4: + parameters: + url: /tasks/task4 + method: GET + task-parameters: + +Workflow: + tasks: + task1: + action: MyRest:task1 + parameters: + + task2: + requires: [task1] + action: MyRest:task2 + parameters: + + task3: + requires: [task1] + action: MyRest:task3 + parameters: + + task4: + requires: [task2, task3] + action: MyRest:task4 + parameters: + + events: + task4: + type: periodic + tasks: task4 + parameters: + cron-pattern: "*/1 * * * *" diff --git a/simple_app/tasks.py b/simple_app/tasks.py new file mode 100644 index 0000000..ee5201d --- /dev/null +++ b/simple_app/tasks.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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 time import sleep +import threading + +from simple_app.api import client + + +CLIENT = client.CLIENT + + +def start_task(**kwargs): + thread = threading.Thread(target=finish_task, kwargs=kwargs) + thread.start() + + +def finish_task(task_id, execution_id, workbook_name): + # simulate working + sleep(8) + + task = CLIENT.tasks.update(workbook_name, execution_id, + task_id, "SUCCESS") + print("Task %s - SUCCESS" % task.name) diff --git a/simple_app/version.py b/simple_app/version.py new file mode 100644 index 0000000..20d2f21 --- /dev/null +++ b/simple_app/version.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 - Mirantis, Inc. +# +# 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 pbr import version + +version_info = version.VersionInfo('simple_app') +version_string = version_info.version_string