Working on REST API
* Added installation/running info into README.md * Added pecan controllers for: * executions * tasks * workbook definitions * Refactored controllers * Added simple functional tests for workbooks Implements: blueprint mistral-rest-api Change-Id: I604b62b8b0bda5f5363b1fefedcb9ffb7447bd1d
This commit is contained in:
parent
039e899d8b
commit
9288a60915
22
README.md
22
README.md
@ -1,4 +1,24 @@
|
||||
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*
|
@ -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
|
64
mistral/api/controllers/resource.py
Normal file
64
mistral/api/controllers/resource.py
Normal 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
|
@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 - Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -16,19 +18,21 @@ import pecan
|
||||
from wsme import types as wtypes
|
||||
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 import resource
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
API_STATUS = wtypes.Enum(str, 'SUPPORTED', 'CURRENT', 'DEPRECATED')
|
||||
|
||||
|
||||
class APIVersion(wtypes.Base):
|
||||
class APIVersion(resource.Resource):
|
||||
"""API Version."""
|
||||
|
||||
id = wtypes.text
|
||||
status = API_STATUS
|
||||
link = common_types.Link
|
||||
link = resource.Link
|
||||
|
||||
|
||||
class RootController(object):
|
||||
@ -37,9 +41,11 @@ class RootController(object):
|
||||
|
||||
@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=common_types.Link(href=host_url, target='v1'))
|
||||
link=resource.Link(href=host_url, target='v1'))
|
||||
|
||||
return [api_v1]
|
||||
|
94
mistral/api/controllers/v1/execution.py
Normal file
94
mistral/api/controllers/v1/execution.py
Normal 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)
|
@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 - Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -12,19 +14,19 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
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 Event(wtypes.Base):
|
||||
class Event(resource.Resource):
|
||||
"""Event descriptor."""
|
||||
pass
|
||||
|
||||
@ -39,8 +41,8 @@ class ExecutionEvent(Event):
|
||||
workbook_name = wtypes.text
|
||||
|
||||
|
||||
class Listener(wtypes.Base):
|
||||
"""Workbook resource."""
|
||||
class Listener(resource.Resource):
|
||||
"""Listener resource."""
|
||||
|
||||
id = wtypes.text
|
||||
description = wtypes.text
|
||||
@ -49,71 +51,55 @@ class Listener(wtypes.Base):
|
||||
events = [Event]
|
||||
|
||||
|
||||
class Listeners(wtypes.Base):
|
||||
"""A collection of Listeners."""
|
||||
class Listeners(resource.Resource):
|
||||
"""A collection of Listener resources."""
|
||||
|
||||
listeners = [Listener]
|
||||
|
||||
def __str__(self):
|
||||
return "Listeners [listeners=%s]" % self.listeners
|
||||
|
||||
|
||||
class ListenersController(rest.RestController):
|
||||
"""Operations on collection of listeners."""
|
||||
|
||||
@wsme_pecan.wsexpose(Listener, wtypes.text, wtypes.text)
|
||||
def get(self, workbook_name, id):
|
||||
LOG.debug("Fetch listener [workbook_name=%s, id=%s]" %
|
||||
(workbook_name, id))
|
||||
|
||||
# TODO: fetch the listener from DB
|
||||
values = db_api.listener_get(workbook_name, id)
|
||||
|
||||
error = "Not implemented"
|
||||
pecan.response.translatable_error = error
|
||||
|
||||
raise wsme.exc.ClientSideError(unicode(error))
|
||||
if not values:
|
||||
abort(404)
|
||||
else:
|
||||
return Listener.from_dict(values)
|
||||
|
||||
@wsme_pecan.wsexpose(Listener, wtypes.text, wtypes.text, body=Listener)
|
||||
def put(self, workbook_name, id, listener):
|
||||
LOG.debug("Update listener [workbook_name=%s, id=%s, listener=%s]" %
|
||||
(workbook_name, id, listener))
|
||||
|
||||
# TODO: modify the listener in DB
|
||||
values = db_api.listener_update(workbook_name, id, listener.to_dict())
|
||||
|
||||
error = "Not implemented"
|
||||
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))
|
||||
return Listener.from_dict(values)
|
||||
|
||||
@wsme_pecan.wsexpose(Listener, wtypes.text, body=Listener, status_code=201)
|
||||
def post(self, workbook_name, listener):
|
||||
LOG.debug("Create listener [workbook_name=%s, listener=%s]" %
|
||||
(workbook_name, listener))
|
||||
|
||||
# TODO: create listener in DB
|
||||
values = db_api.listener_create(workbook_name, listener.to_dict())
|
||||
|
||||
error = "Not implemented"
|
||||
pecan.response.translatable_error = error
|
||||
return Listener.from_dict(values)
|
||||
|
||||
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)
|
||||
def get_all(self, workbook_name):
|
||||
LOG.debug("Fetch listeners [workbook_name=%s]" % workbook_name)
|
||||
|
||||
listeners = []
|
||||
# TODO: fetch listeners from DB
|
||||
listeners = [Listener.from_dict(values)
|
||||
for values in db_api.listeners_get(workbook_name)]
|
||||
|
||||
return Listeners(listeners=listeners)
|
||||
|
@ -17,9 +17,10 @@ from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
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.
|
||||
|
||||
It references all other resources belonging to the API.
|
||||
@ -28,6 +29,7 @@ class RootResource(wtypes.Base):
|
||||
uri = wtypes.text
|
||||
|
||||
# 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):
|
||||
|
79
mistral/api/controllers/v1/task.py
Normal file
79
mistral/api/controllers/v1/task.py
Normal 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)
|
@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 - Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -12,99 +14,77 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from pecan import abort
|
||||
from wsme import types as wtypes
|
||||
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 execution
|
||||
from mistral.api.controllers import resource
|
||||
|
||||
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(wtypes.Base):
|
||||
class Workbook(resource.Resource):
|
||||
"""Workbook resource."""
|
||||
|
||||
name = wtypes.text
|
||||
description = wtypes.text
|
||||
tags = [wtypes.text]
|
||||
|
||||
def __str__(self):
|
||||
return "Workbook [name='%s', description='%s', tags='%s']" % \
|
||||
(self.name, self.description, self.tags)
|
||||
|
||||
|
||||
class Workbooks(wtypes.Base):
|
||||
class Workbooks(resource.Resource):
|
||||
"""A collection of Workbooks."""
|
||||
|
||||
workbooks = [Workbook]
|
||||
|
||||
def __str__(self):
|
||||
return "Workbooks [workbooks=%s]" % self.workbooks
|
||||
|
||||
|
||||
class WorkbooksController(rest.RestController):
|
||||
"""Operations on collection of workbooks."""
|
||||
|
||||
definition = workbook_definition.WorkbookDefinitionController()
|
||||
listeners = listener.ListenersController()
|
||||
|
||||
#@pecan.expose()
|
||||
#def _lookup(self, workbook_name, *remainder):
|
||||
# # Standard Pecan delegation.
|
||||
# return WorkbookController(workbook_name), remainder
|
||||
executions = execution.ExecutionsController()
|
||||
|
||||
@wsme_pecan.wsexpose(Workbook, wtypes.text)
|
||||
def get(self, name):
|
||||
LOG.debug("Fetch workbook [name=%s]" % name)
|
||||
|
||||
# TODO: fetch the workbook from the DB
|
||||
values = db_api.workbook_get(name)
|
||||
|
||||
error = "Not implemented"
|
||||
pecan.response.translatable_error = error
|
||||
|
||||
raise wsme.exc.ClientSideError(unicode(error))
|
||||
if not values:
|
||||
abort(404)
|
||||
else:
|
||||
return Workbook.from_dict(values)
|
||||
|
||||
@wsme_pecan.wsexpose(Workbook, wtypes.text, body=Workbook)
|
||||
def put(self, name, workbook):
|
||||
LOG.debug("Update workbook [name=%s, workbook=%s]" % (name, workbook))
|
||||
|
||||
# TODO: modify the workbook in DB
|
||||
|
||||
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))
|
||||
return Workbook.from_dict(db_api.workbook_update(name,
|
||||
workbook.to_dict()))
|
||||
|
||||
@wsme_pecan.wsexpose(Workbook, body=Workbook, status_code=201)
|
||||
def post(self, 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"
|
||||
pecan.response.translatable_error = error
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
|
||||
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)
|
||||
def get_all(self):
|
||||
LOG.debug("Fetch workbooks.")
|
||||
|
||||
workbooks = []
|
||||
# TODO: fetch workbooks from DB
|
||||
workbooks = [Workbook.from_dict(values)
|
||||
for values in db_api.workbooks_get()]
|
||||
|
||||
return Workbooks(workbooks=workbooks)
|
||||
|
40
mistral/api/controllers/v1/workbook_definition.py
Normal file
40
mistral/api/controllers/v1/workbook_definition.py
Normal 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
117
mistral/db/api.py
Normal 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 [{}]
|
77
mistral/tests/api/v1/controllers/test_executions.py
Normal file
77
mistral/tests/api/v1/controllers/test_executions.py
Normal 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])
|
77
mistral/tests/api/v1/controllers/test_listeners.py
Normal file
77
mistral/tests/api/v1/controllers/test_listeners.py
Normal 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])
|
68
mistral/tests/api/v1/controllers/test_tasks.py
Normal file
68
mistral/tests/api/v1/controllers/test_tasks.py
Normal 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])
|
49
mistral/tests/api/v1/controllers/test_workbook_definition.py
Normal file
49
mistral/tests/api/v1/controllers/test_workbook_definition.py
Normal 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)
|
@ -14,21 +14,62 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from mistral.openstack.common import jsonutils
|
||||
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.
|
||||
|
||||
WORKBOOKS = [
|
||||
{
|
||||
'name': "my_workbook",
|
||||
'description': "My cool Mistral workbook",
|
||||
'tags': ['deployment', 'demo']
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
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):
|
||||
resp = self.app.get('/v1/workbooks',
|
||||
headers={'Accept': 'application/json'})
|
||||
db_api.workbooks_get = mock.MagicMock(return_value=WORKBOOKS)
|
||||
|
||||
resp = self.app.get('/v1/workbooks')
|
||||
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
data = jsonutils.loads(resp.body.decode())
|
||||
|
||||
print "json=%s" % data
|
||||
|
||||
#self.assertEqual(data['name'], 'my_workbook')
|
||||
#self.assertEqual(data['description'], 'My cool workbook')
|
||||
self.assertEqual(len(resp.json), 1)
|
||||
self.assertDictEqual(WORKBOOKS[0], resp.json['workbooks'][0])
|
||||
|
Loading…
Reference in New Issue
Block a user