Merge "Working on REST API"
This commit is contained in:
commit
40d43a6dec
22
README.md
22
README.md
@ -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*
|
@ -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.
|
# 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]
|
||||||
|
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.
|
# 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)
|
||||||
|
@ -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):
|
||||||
|
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.
|
# 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)
|
||||||
|
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
|
# 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')
|
|
||||||
|
Loading…
Reference in New Issue
Block a user