Merge "Working on REST API"

This commit is contained in:
Jenkins 2013-12-02 14:09:14 +00:00 committed by Gerrit Code Review
commit 40d43a6dec
16 changed files with 804 additions and 126 deletions

View File

@ -1,4 +1,24 @@
Mistral Mistral
======= =======
Task service for OpenStack cloud Task Orchestration and Scheduling service for OpenStack cloud
Running in development mode
---------------------------
### Installation
First of all, in a shell run:
*tox*
This will install necessary virtual environments and run all the project tests. Installing virtual environments may take significant time (~10-15 mins).
### Mistral configuration
Open *etc/mistral.conf* file and fix configuration properties as needed. For example, *host* and *port* specified by default may not be desired in a particular environment.
### Running Mistral API server
To run Mistral API server perform the following commands in a shell:
*tox -epy27 -- /mistral/cmd/api.py --config-file etc/mistral.conf*

View File

@ -1,22 +0,0 @@
# 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 wsme import types as wtypes
class Link(wtypes.Base):
"""Web link."""
href = wtypes.text
target = wtypes.text

View File

@ -0,0 +1,64 @@
# -*- 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 wsme import types as wtypes
class Resource(wtypes.Base):
"""REST API Resource."""
def to_dict(self):
# TODO: take care of nested resources
d = {}
for attr in self._wsme_attributes:
d[attr.name] = getattr(self, attr.name)
return d
@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 + "]"
class Link(Resource):
"""Web link."""
href = wtypes.text
target = wtypes.text

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 - Mirantis, Inc. # Copyright 2013 - Mirantis, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -16,19 +18,21 @@ import pecan
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api.controllers import common_types from mistral.openstack.common import log as logging
from mistral.api.controllers.v1 import root as v1_root from mistral.api.controllers.v1 import root as v1_root
from mistral.api.controllers import resource
LOG = logging.getLogger(__name__)
API_STATUS = wtypes.Enum(str, 'SUPPORTED', 'CURRENT', 'DEPRECATED') API_STATUS = wtypes.Enum(str, 'SUPPORTED', 'CURRENT', 'DEPRECATED')
class APIVersion(wtypes.Base): class APIVersion(resource.Resource):
"""API Version.""" """API Version."""
id = wtypes.text id = wtypes.text
status = API_STATUS status = API_STATUS
link = common_types.Link link = resource.Link
class RootController(object): class RootController(object):
@ -37,9 +41,11 @@ class RootController(object):
@wsme_pecan.wsexpose([APIVersion]) @wsme_pecan.wsexpose([APIVersion])
def index(self): def index(self):
LOG.debug("Fetching API versions.")
host_url = '%s/%s' % (pecan.request.host_url, 'v1') host_url = '%s/%s' % (pecan.request.host_url, 'v1')
api_v1 = APIVersion(id='v1.0', api_v1 = APIVersion(id='v1.0',
status='CURRENT', status='CURRENT',
link=common_types.Link(href=host_url, target='v1')) link=resource.Link(href=host_url, target='v1'))
return [api_v1] return [api_v1]

View File

@ -0,0 +1,94 @@
# 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 pecan import rest
from pecan import abort
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api.controllers.v1 import task
from mistral.openstack.common import log as logging
from mistral.api.controllers import resource
from mistral.db import api as db_api
LOG = logging.getLogger(__name__)
class Execution(resource.Resource):
"""Execution resource."""
id = wtypes.text
workbook_name = wtypes.text
target_task = wtypes.text
state = wtypes.text
class Executions(resource.Resource):
"""A collection of Execution resources."""
executions = [Execution]
class ExecutionsController(rest.RestController):
tasks = task.TasksController()
@wsme_pecan.wsexpose(Execution, wtypes.text, wtypes.text)
def get(self, workbook_name, id):
LOG.debug("Fetch execution [workbook_name=%s, id=%s]" %
(workbook_name, id))
values = db_api.execution_get(workbook_name, id)
if not values:
abort(404)
else:
return Execution.from_dict(values)
@wsme_pecan.wsexpose(Execution, wtypes.text, wtypes.text, body=Execution)
def put(self, workbook_name, id, execution):
LOG.debug("Update execution [workbook_name=%s, id=%s, execution=%s]" %
(workbook_name, id, execution))
values = db_api.execution_update(workbook_name,
id,
execution.to_dict())
return Execution.from_dict(values)
@wsme_pecan.wsexpose(Execution, wtypes.text, body=Execution,
status_code=201)
def post(self, workbook_name, execution):
LOG.debug("Create listener [workbook_name=%s, execution=%s]" %
(workbook_name, execution))
values = db_api.execution_create(workbook_name, execution.to_dict())
return Execution.from_dict(values)
@wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, status_code=204)
def delete(self, workbook_name, id):
LOG.debug("Delete execution [workbook_name=%s, id=%s]" %
(workbook_name, id))
db_api.execution_delete(workbook_name, id)
@wsme_pecan.wsexpose(Executions, wtypes.text)
def get_all(self, workbook_name):
LOG.debug("Fetch executions [workbook_name=%s]" % workbook_name)
executions = [Execution.from_dict(values)
for values in db_api.executions_get(workbook_name)]
return Executions(executions=executions)

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 - Mirantis, Inc. # Copyright 2013 - Mirantis, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -12,19 +14,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import pecan
from pecan import rest from pecan import rest
import wsme from pecan import abort
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.openstack.common import log as logging from mistral.openstack.common import log as logging
from mistral.api.controllers import resource
from mistral.db import api as db_api
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class Event(wtypes.Base): class Event(resource.Resource):
"""Event descriptor.""" """Event descriptor."""
pass pass
@ -39,8 +41,8 @@ class ExecutionEvent(Event):
workbook_name = wtypes.text workbook_name = wtypes.text
class Listener(wtypes.Base): class Listener(resource.Resource):
"""Workbook resource.""" """Listener resource."""
id = wtypes.text id = wtypes.text
description = wtypes.text description = wtypes.text
@ -49,71 +51,55 @@ class Listener(wtypes.Base):
events = [Event] events = [Event]
class Listeners(wtypes.Base): class Listeners(resource.Resource):
"""A collection of Listeners.""" """A collection of Listener resources."""
listeners = [Listener] listeners = [Listener]
def __str__(self):
return "Listeners [listeners=%s]" % self.listeners
class ListenersController(rest.RestController): class ListenersController(rest.RestController):
"""Operations on collection of listeners."""
@wsme_pecan.wsexpose(Listener, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(Listener, wtypes.text, wtypes.text)
def get(self, workbook_name, id): def get(self, workbook_name, id):
LOG.debug("Fetch listener [workbook_name=%s, id=%s]" % LOG.debug("Fetch listener [workbook_name=%s, id=%s]" %
(workbook_name, id)) (workbook_name, id))
# TODO: fetch the listener from DB values = db_api.listener_get(workbook_name, id)
error = "Not implemented" if not values:
pecan.response.translatable_error = error abort(404)
else:
raise wsme.exc.ClientSideError(unicode(error)) return Listener.from_dict(values)
@wsme_pecan.wsexpose(Listener, wtypes.text, wtypes.text, body=Listener) @wsme_pecan.wsexpose(Listener, wtypes.text, wtypes.text, body=Listener)
def put(self, workbook_name, id, listener): def put(self, workbook_name, id, listener):
LOG.debug("Update listener [workbook_name=%s, id=%s, listener=%s]" % LOG.debug("Update listener [workbook_name=%s, id=%s, listener=%s]" %
(workbook_name, id, listener)) (workbook_name, id, listener))
# TODO: modify the listener in DB values = db_api.listener_update(workbook_name, id, listener.to_dict())
error = "Not implemented" return Listener.from_dict(values)
pecan.response.translatable_error = error
raise wsme.exc.ClientSideError(unicode(error))
@wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, status_code=204)
def delete(self, workbook_name, id):
LOG.debug("Delete listener [workbook_name=%s, id=%s]" %
(workbook_name, id))
# TODO: delete the listener from DB
error = "Not implemented"
pecan.response.translatable_error = error
raise wsme.exc.ClientSideError(unicode(error))
@wsme_pecan.wsexpose(Listener, wtypes.text, body=Listener, status_code=201) @wsme_pecan.wsexpose(Listener, wtypes.text, body=Listener, status_code=201)
def post(self, workbook_name, listener): def post(self, workbook_name, listener):
LOG.debug("Create listener [workbook_name=%s, listener=%s]" % LOG.debug("Create listener [workbook_name=%s, listener=%s]" %
(workbook_name, listener)) (workbook_name, listener))
# TODO: create listener in DB values = db_api.listener_create(workbook_name, listener.to_dict())
error = "Not implemented" return Listener.from_dict(values)
pecan.response.translatable_error = error
raise wsme.exc.ClientSideError(unicode(error)) @wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, status_code=204)
def delete(self, workbook_name, id):
LOG.debug("Delete listener [workbook_name=%s, id=%s]" %
(workbook_name, id))
db_api.listener_delete(workbook_name, id)
@wsme_pecan.wsexpose(Listeners, wtypes.text) @wsme_pecan.wsexpose(Listeners, wtypes.text)
def get_all(self, workbook_name): def get_all(self, workbook_name):
LOG.debug("Fetch listeners [workbook_name=%s]" % workbook_name) LOG.debug("Fetch listeners [workbook_name=%s]" % workbook_name)
listeners = [] listeners = [Listener.from_dict(values)
# TODO: fetch listeners from DB for values in db_api.listeners_get(workbook_name)]
return Listeners(listeners=listeners) return Listeners(listeners=listeners)

View File

@ -17,9 +17,10 @@ from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api.controllers.v1 import workbook from mistral.api.controllers.v1 import workbook
from mistral.api.controllers import resource
class RootResource(wtypes.Base): class RootResource(resource.Resource):
"""Root resource for API version 1. """Root resource for API version 1.
It references all other resources belonging to the API. It references all other resources belonging to the API.
@ -28,6 +29,7 @@ class RootResource(wtypes.Base):
uri = wtypes.text uri = wtypes.text
# TODO: what else do we need here? # TODO: what else do we need here?
# TODO: we need to collect all the links from API v1.0 and provide them
class Controller(object): class Controller(object):

View File

@ -0,0 +1,79 @@
# 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 pecan import rest
from pecan import abort
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.openstack.common import log as logging
from mistral.api.controllers import resource
from mistral.db import api as db_api
LOG = logging.getLogger(__name__)
class Task(resource.Resource):
"""Task resource."""
id = wtypes.text
workbook_name = wtypes.text
execution_id = wtypes.text
name = wtypes.text
description = wtypes.text
action = wtypes.text
state = wtypes.text
tags = [wtypes.text]
class Tasks(resource.Resource):
"""A collection of tasks."""
tasks = [Task]
class TasksController(rest.RestController):
@wsme_pecan.wsexpose(Task, wtypes.text, wtypes.text, wtypes.text)
def get(self, workbook_name, execution_id, id):
LOG.debug("Fetch task [workbook_name=%s, execution_id=%s, id=%s]" %
(workbook_name, execution_id, id))
values = db_api.task_get(workbook_name, execution_id, id)
if not values:
abort(404)
return Task.from_dict(values)
@wsme_pecan.wsexpose(Task, wtypes.text, wtypes.text, wtypes.text,
body=Task)
def put(self, workbook_name, execution_id, id, task):
LOG.debug("Update task "
"[workbook_name=%s, execution_id=%s, id=%s, task=%s]" %
(workbook_name, execution_id, id, task))
values = db_api.task_update(workbook_name, execution_id, id,
task.to_dict())
return Task.from_dict(values)
@wsme_pecan.wsexpose(Tasks, wtypes.text, wtypes.text)
def get_all(self, workbook_name, execution_id):
LOG.debug("Fetch tasks [workbook_name=%s, execution_id=%s]" %
(workbook_name, execution_id))
tasks = [Task.from_dict(values)
for values in db_api.tasks_get(workbook_name, execution_id)]
return Tasks(tasks=tasks)

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 - Mirantis, Inc. # Copyright 2013 - Mirantis, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -12,99 +14,77 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import pecan
from pecan import rest from pecan import rest
import wsme from pecan import abort
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api.controllers.v1 import workbook_definition
from mistral.api.controllers.v1 import listener from mistral.api.controllers.v1 import listener
from mistral.api.controllers.v1 import execution
from mistral.api.controllers import resource
from mistral.openstack.common import log as logging from mistral.openstack.common import log as logging
from mistral.db import api as db_api
LOG = logging.getLogger(__name__)
LOG = logging.getLogger("%s" % __name__) class Workbook(resource.Resource):
class Workbook(wtypes.Base):
"""Workbook resource.""" """Workbook resource."""
name = wtypes.text name = wtypes.text
description = wtypes.text description = wtypes.text
tags = [wtypes.text] tags = [wtypes.text]
def __str__(self):
return "Workbook [name='%s', description='%s', tags='%s']" % \
(self.name, self.description, self.tags)
class Workbooks(resource.Resource):
class Workbooks(wtypes.Base):
"""A collection of Workbooks.""" """A collection of Workbooks."""
workbooks = [Workbook] workbooks = [Workbook]
def __str__(self):
return "Workbooks [workbooks=%s]" % self.workbooks
class WorkbooksController(rest.RestController): class WorkbooksController(rest.RestController):
"""Operations on collection of workbooks."""
definition = workbook_definition.WorkbookDefinitionController()
listeners = listener.ListenersController() listeners = listener.ListenersController()
executions = execution.ExecutionsController()
#@pecan.expose()
#def _lookup(self, workbook_name, *remainder):
# # Standard Pecan delegation.
# return WorkbookController(workbook_name), remainder
@wsme_pecan.wsexpose(Workbook, wtypes.text) @wsme_pecan.wsexpose(Workbook, wtypes.text)
def get(self, name): def get(self, name):
LOG.debug("Fetch workbook [name=%s]" % name) LOG.debug("Fetch workbook [name=%s]" % name)
# TODO: fetch the workbook from the DB values = db_api.workbook_get(name)
error = "Not implemented" if not values:
pecan.response.translatable_error = error abort(404)
else:
raise wsme.exc.ClientSideError(unicode(error)) return Workbook.from_dict(values)
@wsme_pecan.wsexpose(Workbook, wtypes.text, body=Workbook) @wsme_pecan.wsexpose(Workbook, wtypes.text, body=Workbook)
def put(self, name, workbook): def put(self, name, workbook):
LOG.debug("Update workbook [name=%s, workbook=%s]" % (name, workbook)) LOG.debug("Update workbook [name=%s, workbook=%s]" % (name, workbook))
# TODO: modify the workbook in DB return Workbook.from_dict(db_api.workbook_update(name,
workbook.to_dict()))
error = "Not implemented"
pecan.response.translatable_error = error
raise wsme.exc.ClientSideError(unicode(error))
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name):
LOG.debug("Delete workbook [name=%s]" % name)
# TODO: delete the workbook from DB
error = "Not implemented"
pecan.response.translatable_error = error
raise wsme.exc.ClientSideError(unicode(error))
@wsme_pecan.wsexpose(Workbook, body=Workbook, status_code=201) @wsme_pecan.wsexpose(Workbook, body=Workbook, status_code=201)
def post(self, workbook): def post(self, workbook):
LOG.debug("Create workbook [workbook=%s]" % workbook) LOG.debug("Create workbook [workbook=%s]" % workbook)
# TODO: create the listener in DB return Workbook.from_dict(db_api.workbook_create(workbook.to_dict()))
error = "Not implemented" @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
pecan.response.translatable_error = error def delete(self, name):
LOG.debug("Delete workbook [name=%s]" % name)
raise wsme.exc.ClientSideError(unicode(error)) db_api.workbook_delete(name)
@wsme_pecan.wsexpose(Workbooks) @wsme_pecan.wsexpose(Workbooks)
def get_all(self): def get_all(self):
LOG.debug("Fetch workbooks.") LOG.debug("Fetch workbooks.")
workbooks = [] workbooks = [Workbook.from_dict(values)
# TODO: fetch workbooks from DB for values in db_api.workbooks_get()]
return Workbooks(workbooks=workbooks) return Workbooks(workbooks=workbooks)

View File

@ -0,0 +1,40 @@
# 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 pecan import rest
from pecan import expose
from pecan import request
from mistral.openstack.common import log as logging
from mistral.db import api as db_api
LOG = logging.getLogger(__name__)
class WorkbookDefinitionController(rest.RestController):
@expose()
def get(self, workbook_name):
LOG.debug("Fetch workbook definition [workbook_name=%s]" %
workbook_name)
return db_api.workbook_definition_get(workbook_name)
@expose(content_type="text/plain")
def put(self, workbook_name):
text = request.text
LOG.debug("Update workbook definition [workbook_name=%s, text=%s]" %
(workbook_name, text))
return db_api.workbook_definition_put(workbook_name, text)

117
mistral/db/api.py Normal file
View File

@ -0,0 +1,117 @@
# -*- 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.
# TODO: replace this module later with a real implementation
# Workbooks
def workbook_get(name):
return {}
def workbook_create(values):
return values
def workbook_update(name, values):
return values
def workbook_delete(name):
pass
def workbooks_get():
return [{}]
def workbook_definition_get(workbook_name):
return ""
def workbook_definition_put(workbook_name, text):
return text
# Executions
def execution_get(workbook_name, id):
return {}
def execution_create(workbook_name, values):
return values
def execution_update(workbook_name, id, values):
return values
def execution_delete(workbook_name, id):
pass
def executions_get(workbook_name):
return [{}]
# Tasks
def task_get(workbook_name, execution_id, id):
return {}
def task_create(workbook_name, execution_id, values):
return values
def task_update(workbook_name, execution_id, id, values):
return values
def task_delete(workbook_name, execution_id, id):
pass
def tasks_get(workbook_name, execution_id):
return [{}]
# Listeners
def listener_get(workbook_name, id):
return {}
def listener_create(workbook_name, values):
values['id'] = 1
return values
def listener_update(workbook_name, id, values):
return values
def listener_delete(workbook_name, id):
pass
def listeners_get(workbook_name):
return [{}]

View File

@ -0,0 +1,77 @@
# -*- 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
from mistral.tests.api import base
from mistral.db import api as db_api
# TODO: later we need additional tests verifying all the errors etc.
EXECS = [
{
'id': "123",
'workbook_name': "my_workbook",
'target_task': 'my_task',
'state': 'RUNNING'
}
]
class TestExecutionsController(base.FunctionalTest):
def test_get(self):
db_api.execution_get = mock.MagicMock(return_value=EXECS[0])
resp = self.app.get('/v1/workbooks/my_workbook/executions/123')
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(EXECS[0], resp.json)
def test_put(self):
updated_exec = EXECS[0].copy()
updated_exec['state'] = 'STOPPED'
db_api.execution_update = mock.MagicMock(return_value=updated_exec)
resp = self.app.put_json('/v1/workbooks/my_workbook/executions/123',
dict(state='STOPPED'))
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(updated_exec, resp.json)
def test_post(self):
db_api.execution_create = mock.MagicMock(return_value=EXECS[0])
resp = self.app.post_json('/v1/workbooks/my_workbook/executions',
EXECS[0])
self.assertEqual(resp.status_int, 201)
self.assertDictEqual(EXECS[0], resp.json)
def test_delete(self):
resp = self.app.delete('/v1/workbooks/my_workbook/executions/123')
self.assertEqual(resp.status_int, 204)
def test_get_all(self):
db_api.executions_get = mock.MagicMock(return_value=EXECS)
resp = self.app.get('/v1/workbooks/my_workbook/executions')
self.assertEqual(resp.status_int, 200)
self.assertEqual(len(resp.json), 1)
self.assertDictEqual(EXECS[0], resp.json['executions'][0])

View File

@ -0,0 +1,77 @@
# -*- 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
from mistral.tests.api import base
from mistral.db import api as db_api
# TODO: later we need additional tests verifying all the errors etc.
LISTENERS = [
{
'id': "1",
'workbook_name': "my_workbook",
'description': "My cool Mistral workbook",
'webhook': "http://my.website.org"
}
]
class TestListenersController(base.FunctionalTest):
def test_get(self):
db_api.listener_get = mock.MagicMock(return_value=LISTENERS[0])
resp = self.app.get('/v1/workbooks/my_workbook/listeners/1')
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(LISTENERS[0], resp.json)
def test_put(self):
updated_lsnr = LISTENERS[0].copy()
updated_lsnr['description'] = 'new description'
db_api.listener_update = mock.MagicMock(return_value=updated_lsnr)
resp = self.app.put_json('/v1/workbooks/my_workbook/listeners/1',
dict(description='new description'))
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(updated_lsnr, resp.json)
def test_post(self):
db_api.listener_create = mock.MagicMock(return_value=LISTENERS[0])
resp = self.app.post_json('/v1/workbooks/my_workbook/listeners',
LISTENERS[0])
self.assertEqual(resp.status_int, 201)
self.assertDictEqual(LISTENERS[0], resp.json)
def test_delete(self):
resp = self.app.delete('/v1/workbooks/my_workbook/listeners/1')
self.assertEqual(resp.status_int, 204)
def test_get_all(self):
db_api.listeners_get = mock.MagicMock(return_value=LISTENERS)
resp = self.app.get('/v1/workbooks/my_workbook/listeners')
self.assertEqual(resp.status_int, 200)
self.assertEqual(len(resp.json), 1)
self.assertDictEqual(LISTENERS[0], resp.json['listeners'][0])

View File

@ -0,0 +1,68 @@
# -*- 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
from mistral.tests.api import base
from mistral.db import api as db_api
# TODO: later we need additional tests verifying all the errors etc.
TASKS = [
{
'id': "1",
'workbook_name': "my_workbook",
'execution_id': '123',
'name': 'my_task',
'description': 'My cool task',
'action': 'my_action',
'state': 'RUNNING',
'tags': ['deployment', 'demo']
}
]
class TestTasksController(base.FunctionalTest):
def test_get(self):
db_api.task_get = mock.MagicMock(return_value=TASKS[0])
resp = self.app.get('/v1/workbooks/my_workbook/executions/123/tasks/1')
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(TASKS[0], resp.json)
def test_put(self):
updated_task = TASKS[0].copy()
updated_task['state'] = 'STOPPED'
db_api.task_update = mock.MagicMock(return_value=updated_task)
resp = self.app.put_json(
'/v1/workbooks/my_workbook/executions/123/tasks/1',
dict(state='STOPPED'))
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(updated_task, resp.json)
def test_get_all(self):
db_api.tasks_get = mock.MagicMock(return_value=TASKS)
resp = self.app.get('/v1/workbooks/my_workbook/executions/123/tasks')
self.assertEqual(resp.status_int, 200)
self.assertEqual(len(resp.json), 1)
self.assertDictEqual(TASKS[0], resp.json['tasks'][0])

View File

@ -0,0 +1,49 @@
# -*- 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
from mistral.tests.api import base
from mistral.db import api as db_api
# TODO: later we need additional tests verifying all the errors etc.
DEFINITION = "my definition"
class TestWorkbookDefinitionController(base.FunctionalTest):
def test_get(self):
db_api.workbook_definition_get =\
mock.MagicMock(return_value=DEFINITION)
resp = self.app.get('/v1/workbooks/my_workbook/definition',
headers={"Content-Type": "text/plain"})
self.assertEqual(resp.status_int, 200)
self.assertEqual(DEFINITION, resp.text)
def test_put(self):
new_definition = "new definition"
db_api.workbook_definition_update =\
mock.MagicMock(return_value=new_definition)
resp = self.app.put('/v1/workbooks/my_workbook/definition',
new_definition,
headers={"Content-Type": "text/plain"})
self.assertEqual(resp.status_int, 200)
self.assertEqual(new_definition, resp.body)

View File

@ -14,21 +14,62 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from mistral.openstack.common import jsonutils import mock
from mistral.tests.api import base from mistral.tests.api import base
from mistral.db import api as db_api
# TODO: later we need additional tests verifying all the errors etc.
WORKBOOKS = [
{
'name': "my_workbook",
'description': "My cool Mistral workbook",
'tags': ['deployment', 'demo']
}
]
class TestWorkbooksController(base.FunctionalTest): class TestWorkbooksController(base.FunctionalTest):
def test_get(self):
db_api.workbook_get = mock.MagicMock(return_value=WORKBOOKS[0])
resp = self.app.get('/v1/workbooks/my_workbook')
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(WORKBOOKS[0], resp.json)
def test_put(self):
updated_workbook = WORKBOOKS[0].copy()
updated_workbook['description'] = 'new description'
db_api.workbook_update = mock.MagicMock(return_value=updated_workbook)
resp = self.app.put_json('/v1/workbooks/my_workbook',
dict(description='new description'))
self.assertEqual(resp.status_int, 200)
self.assertDictEqual(updated_workbook, resp.json)
def test_post(self):
db_api.workbook_create = mock.MagicMock(return_value=WORKBOOKS[0])
resp = self.app.post_json('/v1/workbooks', WORKBOOKS[0])
self.assertEqual(resp.status_int, 201)
self.assertDictEqual(WORKBOOKS[0], resp.json)
def test_delete(self):
resp = self.app.delete('/v1/workbooks/my_workbook')
self.assertEqual(resp.status_int, 204)
def test_get_all(self): def test_get_all(self):
resp = self.app.get('/v1/workbooks', db_api.workbooks_get = mock.MagicMock(return_value=WORKBOOKS)
headers={'Accept': 'application/json'})
resp = self.app.get('/v1/workbooks')
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
data = jsonutils.loads(resp.body.decode()) self.assertEqual(len(resp.json), 1)
self.assertDictEqual(WORKBOOKS[0], resp.json['workbooks'][0])
print "json=%s" % data
#self.assertEqual(data['name'], 'my_workbook')
#self.assertEqual(data['description'], 'My cool workbook')