Skip to content

Commit

Permalink
Allow clients to modify model state, result, progress, and message th…
Browse files Browse the repository at this point in the history
…rough the API, and create separate API to start internal computation. Closes #105.
  • Loading branch information
tshead2 committed Dec 2, 2013
1 parent 7cf7d70 commit d9045e6
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 58 deletions.
14 changes: 11 additions & 3 deletions packages/slycat/web/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,17 +177,25 @@ def store_array_attribute(self, mid, name, array, attribute, data, ranges=None):
ranges = [(0, len(data))]
self.request("PUT", "/models/%s/array-sets/%s/arrays/%s/attributes/%s" % (mid, name, array, attribute), data={}, files={"ranges" : json.dumps(ranges), "data":json.dumps(data)})

def set_progress(self, mid, progress):
def set_model_state(self, mid, state):
"""Sets the current model state."""
self.request("PUT", "/models/%s" % (mid), headers={"content-type":"application/json"}, data=json.dumps({"state": require_string(state)}))

def set_model_result(self, mid, result):
"""Sets the current model result."""
self.request("PUT", "/models/%s" % (mid), headers={"content-type":"application/json"}, data=json.dumps({"result": require_string(result)}))

def set_model_progress(self, mid, progress):
"""Sets the current model progress."""
self.request("PUT", "/models/%s" % (mid), headers={"content-type":"application/json"}, data=json.dumps({"progress": require_float(progress)}))

def set_message(self, mid, message):
def set_model_message(self, mid, message):
"""Sets the current model message."""
self.request("PUT", "/models/%s" % (mid), headers={"content-type":"application/json"}, data=json.dumps({"message": require_string(message)}))

def finish_model(self, mid):
"""Completes a model."""
self.request("PUT", "/models/%s" % (mid), headers={"content-type":"application/json"}, data=json.dumps({"state":"running"}))
self.request("POST", "/models/%s/finish" % (mid))

def get_model(self, mid):
"""Returns a single model."""
Expand Down
5 changes: 4 additions & 1 deletion packages/slycat/web/server/database/couchdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def put_attachment(self, *arguments, **keywords):
return self.database.put_attachment(*arguments, **keywords)

def save(self, *arguments, **keywords):
return self.database.save(*arguments, **keywords)
try:
return self.database.save(*arguments, **keywords)
except couchdb.http.ServerError as e:
raise cherrypy.HTTPError("%s %s" % (e.message[0], e.message[1][1]))

def view(self, *arguments, **keywords):
return self.database.view(*arguments, **keywords)
Expand Down
1 change: 1 addition & 0 deletions packages/slycat/web/server/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def start(config_file="config.ini"):
dispatcher.connect("get-user", "/users/:uid", slycat.web.server.handlers.get_user, conditions={"method" : ["GET"]})
dispatcher.connect("post-browse", "/browse", slycat.web.server.handlers.post_browse, conditions={"method" : ["POST"]})
dispatcher.connect("post-events", "/events/{event:.*}", slycat.web.server.handlers.post_events, conditions={"method" : ["POST"]})
dispatcher.connect("post-model-finish", "/models/:mid/finish", slycat.web.server.handlers.post_model_finish, conditions={"method" : ["POST"]})
dispatcher.connect("post-project-bookmarks", "/projects/:pid/bookmarks", slycat.web.server.handlers.post_project_bookmarks, conditions={"method" : ["POST"]})
dispatcher.connect("post-project-models", "/projects/:pid/models", slycat.web.server.handlers.post_project_models, conditions={"method" : ["POST"]})
dispatcher.connect("post-projects", "/projects", slycat.web.server.handlers.post_projects, conditions={"method" : ["POST"]})
Expand Down
46 changes: 21 additions & 25 deletions packages/slycat/web/server/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,40 +364,36 @@ def put_model(mid):
slycat.web.server.authentication.require_project_writer(project)

save_model = False
finish_model = False
for key, value in cherrypy.request.json.items():
if key in ["name", "description", "progress", "message"]:
save_model = True
model[key] = value
elif key == "state":
if value == model["state"]:
pass
elif value == "closed" and model["state"] in ["waiting", "finished"]:
save_model = True
if key in ["name", "description", "state", "result", "progress", "message"]:
if value != model.get(key):
model[key] = value
elif value == "running" and model["state"] in ["waiting"]:
finish_model = True
else:
raise cherrypy.HTTPError("400 Not an allowed model state transition: %s to %s" % (model["state"], value))
save_model = True
else:
raise cherrypy.HTTPError("400 Unknown model parameter: %s" % key)

if save_model:
database.save(model)

if finish_model:
cherrypy.response.status = "202 Finishing model."
def post_model_finish(mid):
database = slycat.web.server.database.couchdb.connect()
model = database.get("model", mid)
project = database.get("project", model["project"])
slycat.web.server.authentication.require_project_writer(project)

slycat.web.server.model.update(database, model, state="running", started = datetime.datetime.utcnow().isoformat(), progress = 0.0)
if model["state"] != "waiting":
raise cherrypy.HTTPError("400 Only waiting models can be finished.")
if model["model-type"] not in ["generic", "cca", "cca3", "timeseries"]:
raise cherrypy.HTTPError("500 Cannot finish unknown model type.")

if model["model-type"] == "generic":
slycat.web.server.model.generic.finish(database, model)
elif model["model-type"] == "cca":
slycat.web.server.model.cca.finish(database, model)
elif model["model-type"] == "timeseries":
slycat.web.server.model.timeseries.finish(database, model)
else:
raise cherrypy.HTTPError("500 Cannot finish unknown model type")
slycat.web.server.model.update(database, model, state="running", started = datetime.datetime.utcnow().isoformat(), progress = 0.0)
if model["model-type"] == "generic":
slycat.web.server.model.generic.finish(database, model)
elif model["model-type"] == "cca":
slycat.web.server.model.cca.finish(database, model)
elif model["model-type"] == "timeseries":
slycat.web.server.model.timeseries.finish(database, model)
cherrypy.response.status = "202 Finishing model."

@cherrypy.tools.json_in(on = True)
def put_model_inputs(mid):
Expand All @@ -421,7 +417,7 @@ def put_model_table(mid, name, input=None, file=None, username=None, hostname=No

if input is None:
raise cherrypy.HTTPError("400 Required input parameter is missing.")
input = True if int(input) else False
input = True if input == "true" else False

if file is not None and username is None and hostname is None and password is None and path is None:
data = file.file.read()
Expand Down
18 changes: 17 additions & 1 deletion web-server/couchdb-design/validate_doc_update
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,30 @@ function(new_document, old_document, user_context)
require(new_document["creator"] != null, "Model must contain creator.");
require(new_document["marking"] != null, "Model must contain marking information.");
require(new_document["name"] != null, "Model must have a name.");
require(new_document["model-type"] != null, "Model must have a model-type.")
require(new_document["model-type"] != null, "Model must have a model-type.");
require(new_document["state"] == null || new_document["state"] == "waiting" || new_document["state"] == "running" || new_document["state"] == "finished" || new_document["state"] == "closed", "Invalid model state.");
require(new_document["result"] == null || new_document["result"] == "succeeded" || new_document["result"] == "failed", "Invalid model result.");

if(old_document)
{
require(new_document["state"] == null || new_document["state"] == "waiting" || new_document["state"] == "running" || new_document["state"] == "finished" || new_document["state"] == "closed", "Invalid model state.");
require(new_document["project"] == old_document["project"], "Model project id cannot be modified.");
require(new_document["created"] == old_document["created"], "Model creation time cannot be modified.");
require(new_document["creator"] == old_document["creator"], "Model creator creator cannot be modified.");
require(new_document["marking"] == old_document["marking"], "Model marking cannot be modified.");

if(new_document["state"] != old_document["state"])
{
if(new_document["state"] == "closed")
require(old_document["state"] == "finished" || old_document["state"] == "waiting", "Invalid model state transition.");
else if(new_document["state"] == "finished")
require(old_document["state"] == "running", "Invalid model state transition.");
else if(new_document["state"] == "running")
require(old_document["state"] == "waiting", "Invalid model state transition.");
}

if(old_document["result"] != null)
require(new_document["result"] == old_document["result"], "Model result cannot be modified.");
}
}
else if(new_document["type"] == "hdf5")
Expand Down
10 changes: 2 additions & 8 deletions web-server/templates/model-cca3.html
Original file line number Diff line number Diff line change
Expand Up @@ -611,14 +611,8 @@ <h2>{{name}}</h2>

$.ajax(
{
type : "PUT",
url : "{{server-root}}models/" + rerun_cca_model,
contentType : "application/json",
data: $.toJSON(
{
state: "running",
}),
processData: false,
type : "POST",
url : "{{server-root}}models/" + rerun_cca_model + "/finish",
success: function(result)
{
rerun_cca_model = null;
Expand Down
20 changes: 4 additions & 16 deletions web-server/templates/project.html
Original file line number Diff line number Diff line change
Expand Up @@ -553,14 +553,8 @@ <h3 class="main-title"><a class="model-link" href="{{server-root}}models/{{_id}}

$.ajax(
{
type : "PUT",
url : "{{server-root}}models/" + local_cca_model,
contentType : "application/json",
data: $.toJSON(
{
state: "running",
}),
processData: false,
type : "POST",
url : "{{server-root}}models/" + local_cca_model + "/finish",
success: function(result)
{
local_cca_model = null;
Expand Down Expand Up @@ -786,14 +780,8 @@ <h3 class="main-title"><a class="model-link" href="{{server-root}}models/{{_id}}

$.ajax(
{
type : "PUT",
url : "{{server-root}}models/" + remote_cca_model,
contentType : "application/json",
data: $.toJSON(
{
state : "running",
}),
processData: false,
type : "POST",
url : "{{server-root}}models/" + remote_cca_model + "/finish",
success: function(result)
{
remote_cca_model = null;
Expand Down
80 changes: 76 additions & 4 deletions web-server/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,85 @@ def test_model_state():
pid = connection.create_project("model-state-project")
mid = connection.create_model(pid, "generic", "model-state-model")

connection.set_progress(mid, 0.0)
connection.set_message(mid, "Test.")

model = connection.get_model(mid)
nose.tools.assert_equal(model["state"], "waiting")

with nose.tools.assert_raises(requests.HTTPError):
connection.set_model_state(mid, "bull")

with nose.tools.assert_raises(requests.HTTPError):
connection.set_model_state(mid, "finished")

connection.set_model_state(mid, "running")
model = connection.get_model(mid)
nose.tools.assert_equal(model["state"], "running")

with nose.tools.assert_raises(requests.HTTPError):
connection.set_model_state(mid, "closed")

connection.set_model_state(mid, "finished")
model = connection.get_model(mid)
nose.tools.assert_equal(model["state"], "finished")

connection.set_model_state(mid, "closed")
model = connection.get_model(mid)
nose.tools.assert_equal(model["state"], "closed")

connection.delete_model(mid)
connection.delete_project(pid)

def test_model_result():
pid = connection.create_project("model-result-project")
mid = connection.create_model(pid, "generic", "model-result-model")

model = connection.get_model(mid)
nose.tools.assert_equal(model.get("result"), None)

with nose.tools.assert_raises(requests.HTTPError):
connection.set_model_result(mid, "bull")

connection.set_model_result(mid, "succeeded")
model = connection.get_model(mid)
nose.tools.assert_equal(model["result"], "succeeded")

with nose.tools.assert_raises(requests.HTTPError):
connection.set_model_result(mid, "failed")

connection.delete_model(mid)
connection.delete_project(pid)

def test_model_progress():
pid = connection.create_project("model-progress-project")
mid = connection.create_model(pid, "generic", "model-progress-model")

model = connection.get_model(mid)
nose.tools.assert_equal(model.get("progress", None), None)

connection.set_model_progress(mid, 0.0)
model = connection.get_model(mid)
nose.tools.assert_equal(model["progress"], 0.0)
nose.tools.assert_equal(model["message"], "Test.")

connection.set_model_progress(mid, 1.0)
model = connection.get_model(mid)
nose.tools.assert_equal(model["progress"], 1.0)

connection.delete_model(mid)
connection.delete_project(pid)

def test_model_message():
pid = connection.create_project("model-message-project")
mid = connection.create_model(pid, "generic", "model-message-model")

model = connection.get_model(mid)
nose.tools.assert_equal(model.get("message", None), None)

connection.set_model_message(mid, "test 1")
model = connection.get_model(mid)
nose.tools.assert_equal(model["message"], "test 1")

connection.set_model_message(mid, "test 2")
model = connection.get_model(mid)
nose.tools.assert_equal(model["message"], "test 2")

connection.delete_model(mid)
connection.delete_project(pid)
Expand Down

0 comments on commit d9045e6

Please sign in to comment.