opentox-ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ # class overwrites aka monkey patches
2
+ # hack: store sinatra in global var to make url_for and halt methods accessible
3
+ before{ $sinatra = self unless $sinatra }
4
+
5
+ class Sinatra::Base
6
+ # overwriting halt to log halts (!= 202)
7
+ def halt(*response)
8
+ LOGGER.error "halt "+response.first.to_s+" "+(response.size>1 ? response[1].to_s : "") if response and response.first and response.first >= 300
9
+ # orig sinatra code:
10
+ response = response.first if response.length == 1
11
+ throw :halt, response
12
+ end
13
+ end
14
+
@@ -0,0 +1,168 @@
1
+
2
+
3
+ module OpenTox
4
+
5
+ #PENDING: implement ot error api, move to own file
6
+ class Error
7
+
8
+ attr_accessor :code, :body, :uri, :payload, :headers
9
+
10
+ def initialize(code, body, uri, payload, headers)
11
+ self.code = code
12
+ self.body = body.to_s[0..1000]
13
+ self.uri = uri
14
+ self.payload = payload
15
+ self.headers = headers
16
+ end
17
+
18
+ def self.parse(error_array_string)
19
+ begin
20
+ err = YAML.load(error_array_string)
21
+ if err and err.is_a?(Array) and err.size>0 and err[0].is_a?(Error)
22
+ return err
23
+ else
24
+ return nil
25
+ end
26
+ rescue
27
+ return nil
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ class WrapperResult < String
34
+ attr_accessor :content_type, :code
35
+ end
36
+
37
+ class RestClientWrapper
38
+
39
+ def self.get(uri, headers=nil, wait=true)
40
+ execute( "get", uri, headers, nil, wait)
41
+ end
42
+
43
+ def self.post(uri, headers, payload=nil, wait=true)
44
+ execute( "post", uri, headers, payload, wait )
45
+ end
46
+
47
+ def self.put(uri, headers, payload=nil )
48
+ execute( "put", uri, headers, payload )
49
+ end
50
+
51
+ def self.delete(uri, headers=nil)
52
+ execute( "delete", uri, headers, nil)
53
+ end
54
+
55
+ def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil)
56
+ do_halt( "-", error_msg, uri, headers, payload )
57
+ end
58
+
59
+ private
60
+ def self.execute( rest_call, uri, headers, payload=nil, wait=true )
61
+
62
+ do_halt 400,"uri is null",uri,headers,payload unless uri
63
+ do_halt 400,"not a uri",uri,headers,payload unless Utils.is_uri?(uri)
64
+ do_halt 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash)
65
+ do_halt 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil
66
+ headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems
67
+
68
+ begin
69
+ #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect
70
+ resource = RestClient::Resource.new(uri,{:timeout => 60}) #, :user => @@users[:users].keys[0], :password => @@users[:users].values[0]})
71
+ if payload
72
+ result = resource.send(rest_call, payload, headers)
73
+ elsif headers
74
+ result = resource.send(rest_call, headers)
75
+ else
76
+ result = resource.send(rest_call)
77
+ end
78
+
79
+ # result is a string, with the additional fields content_type and code
80
+ res = WrapperResult.new(result.body)
81
+ res.content_type = result.headers[:content_type]
82
+ raise "content-type not set" unless res.content_type
83
+ res.code = result.code
84
+
85
+ return res if res.code==200 || !wait
86
+
87
+ while (res.code==201 || res.code==202)
88
+ res = wait_for_task(res, uri)
89
+ end
90
+ raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200
91
+ return res
92
+
93
+ rescue RestClient::RequestTimeout => ex
94
+ do_halt 408,ex.message,uri,headers,payload
95
+ rescue => ex
96
+ #raise ex
97
+ #raise "'"+ex.message+"' uri: "+uri.to_s
98
+ begin
99
+ code = ex.http_code
100
+ msg = ex.http_body
101
+ rescue
102
+ code = 500
103
+ msg = ex.to_s
104
+ end
105
+ do_halt code,msg,uri,headers,payload
106
+ end
107
+ end
108
+
109
+ def self.wait_for_task( res, base_uri )
110
+
111
+ task = nil
112
+ case res.content_type
113
+ when /application\/rdf\+xml|application\/x-yaml/
114
+ task = OpenTox::Task.from_data(res, res.content_type, res.code, base_uri)
115
+ when /text\//
116
+ raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and
117
+ res.split("\n").size > 1 #if uri list contains more then one uri, its not a task
118
+ task = OpenTox::Task.find(res.to_s) if Utils.task_uri?(res)
119
+ else
120
+ raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s
121
+ end
122
+
123
+ LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion"
124
+ task.wait_for_completion
125
+ raise task.description unless task.completed? # maybe task was cancelled / error
126
+
127
+ res = WrapperResult.new task.resultURI
128
+ res.code = task.http_code
129
+ res.content_type = "text/uri-list"
130
+ return res
131
+ end
132
+
133
+ def self.do_halt( code, body, uri, headers, payload=nil )
134
+
135
+ #build error
136
+ causing_errors = Error.parse(body)
137
+ if causing_errors
138
+ error = causing_errors + [Error.new(code, "subsequent error", uri, payload, headers)]
139
+ else
140
+ error = [Error.new(code, body, uri, payload, headers)]
141
+ end
142
+
143
+ #debug utility: write error to file
144
+ error_dir = "/tmp/ot_errors"
145
+ FileUtils.mkdir(error_dir) unless File.exist?(error_dir)
146
+ raise "could not create error dir" unless File.exist?(error_dir) and File.directory?(error_dir)
147
+ file_name = "error"
148
+ time=Time.now.strftime("%m.%d.%Y-%H:%M:%S")
149
+ count = 1
150
+ count+=1 while File.exist?(File.join(error_dir,file_name+"_"+time+"_"+count.to_s))
151
+ File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body)
152
+
153
+ # handle error
154
+ # we are either in a task, or in sinatra
155
+ # PENDING: always return yaml for now
156
+
157
+ if $self_task #this global var in Task.as_task to mark that the current process is running in a task
158
+ raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.as_task
159
+ #elsif $sinatra #else halt sinatra
160
+ #$sinatra.halt(502,error.to_yaml)
161
+ elsif defined?(halt)
162
+ halt(502,error.to_yaml)
163
+ else #for testing purposes (if classes used directly)
164
+ raise error.to_yaml
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,83 @@
1
+ # A way to cleanly handle process forking in Sinatra when using Passenger, aka "sporking some code".
2
+ # This will allow you to properly execute some code asynchronously, which otherwise does not work correctly.
3
+ #
4
+ # Written by Ron Evans
5
+ # More info at http://deadprogrammersociety.com
6
+ #
7
+ # Mostly lifted from the Spawn plugin for Rails (http://github.com/tra/spawn)
8
+ # but with all of the Rails stuff removed.... cause you are using Sinatra. If you are using Rails, Spawn is
9
+ # what you need. If you are using something else besides Sinatra that is Rack-based under Passenger, and you are having trouble with
10
+ # asynch processing, let me know if spork helped you.
11
+ #
12
+ module Spork
13
+ # things to close in child process
14
+ @@resources = []
15
+ def self.resources
16
+ @@resources
17
+ end
18
+
19
+ # set the resource to disconnect from in the child process (when forking)
20
+ def self.resource_to_close(resource)
21
+ @@resources << resource
22
+ end
23
+
24
+ # close all the resources added by calls to resource_to_close
25
+ def self.close_resources
26
+ @@resources.each do |resource|
27
+ resource.close if resource && resource.respond_to?(:close) && !resource.closed?
28
+ end
29
+ @@resources = []
30
+ end
31
+
32
+ # actually perform the fork... er, spork
33
+ # valid options are:
34
+ # :priority => to set the process priority of the child
35
+ # :logger => a logger object to use from the child
36
+ # :no_detach => true if you want to keep the child process under the parent control. usually you do NOT want this
37
+ def self.spork(options={})
38
+ logger = options[:logger]
39
+ logger.debug "spork> parent PID = #{Process.pid}" if logger
40
+
41
+ child = fork do
42
+ begin
43
+ start = Time.now
44
+ logger.debug "spork> child PID = #{Process.pid}" if logger
45
+
46
+ # set the nice priority if needed
47
+ Process.setpriority(Process::PRIO_PROCESS, 0, options[:priority]) if options[:priority]
48
+
49
+ # disconnect from the rack
50
+ Spork.close_resources
51
+
52
+ # run the block of code that takes so long
53
+ yield
54
+
55
+ rescue => ex
56
+ #raise ex
57
+ logger.error "spork> Exception in child[#{Process.pid}] - #{ex.class}: #{ex.message}" if logger
58
+ ensure
59
+ logger.info "spork> child[#{Process.pid}] took #{Time.now - start} sec" if logger
60
+ # this form of exit doesn't call at_exit handlers
61
+ exit!(0)
62
+ end
63
+ end
64
+
65
+ # detach from child process (parent may still wait for detached process if they wish)
66
+ Process.detach(child) unless options[:no_detach]
67
+
68
+ return child
69
+ end
70
+
71
+ end
72
+
73
+ # Patch to work with passenger
74
+ if defined? Passenger::Rack::RequestHandler
75
+ class Passenger::Rack::RequestHandler
76
+ alias_method :orig_process_request, :process_request
77
+ def process_request(env, input, output)
78
+ Spork.resource_to_close(input)
79
+ Spork.resource_to_close(output)
80
+ orig_process_request(env, input, output)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,176 @@
1
+ $self_task=nil
2
+
3
+ module OpenTox
4
+
5
+ class Task
6
+
7
+ # due_to_time is only set in local tasks
8
+ TASK_ATTRIBS = [ :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time ]
9
+ TASK_ATTRIBS.each{ |a| attr_accessor(a) }
10
+ attr_accessor :http_code
11
+
12
+ private
13
+ def initialize(uri)
14
+ @uri = uri.to_s.strip
15
+ end
16
+
17
+ # create is private now, use OpenTox::Task.as_task
18
+ def self.create( params )
19
+ task_uri = RestClientWrapper.post(@@config[:services]["opentox-task"], params, nil, false).to_s
20
+ Task.find(task_uri.chomp)
21
+ end
22
+
23
+ public
24
+ def self.find( uri, accept_header=nil )
25
+ task = Task.new(uri)
26
+ task.reload( accept_header )
27
+ return task
28
+ end
29
+
30
+ def self.from_data(data, content_type, code, base_uri)
31
+ task = Task.new(nil)
32
+ task.http_code = code
33
+ task.reload_from_data(data, content_type, base_uri)
34
+ return task
35
+ end
36
+
37
+ def reload( accept_header=nil )
38
+ unless accept_header
39
+ if (@@config[:yaml_hosts].include?(URI.parse(uri).host))
40
+ accept_header = "application/x-yaml"
41
+ else
42
+ accept_header = 'application/rdf+xml'
43
+ end
44
+ end
45
+ result = RestClientWrapper.get(uri, {:accept => accept_header}, false)#'application/x-yaml'})
46
+ @http_code = result.code
47
+ reload_from_data(result, result.content_type, uri)
48
+ end
49
+
50
+ def reload_from_data( data, content_type, base_uri )
51
+ case content_type
52
+ when /yaml/
53
+ task = YAML.load data
54
+ TASK_ATTRIBS.each do |a|
55
+ raise "task yaml data invalid, key missing: "+a.to_s unless task.has_key?(a)
56
+ send("#{a.to_s}=".to_sym,task[a])
57
+ end
58
+ when /application\/rdf\+xml/
59
+ owl = OpenTox::Owl.from_data(data,base_uri,"Task")
60
+ self.uri = owl.uri
61
+ (TASK_ATTRIBS-[:uri]).each{|a| self.send("#{a.to_s}=".to_sym, owl.get(a.to_s))}
62
+ else
63
+ raise "content type for tasks not supported: "+content_type.to_s
64
+ end
65
+ raise "uri is null after loading" unless @uri and @uri.to_s.strip.size>0
66
+ end
67
+
68
+ def cancel
69
+ RestClientWrapper.put(File.join(@uri,'Cancelled'))
70
+ reload
71
+ end
72
+
73
+ def completed(uri)
74
+ RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri})
75
+ reload
76
+ end
77
+
78
+ def error(description)
79
+ RestClientWrapper.put(File.join(@uri,'Error'),{:description => description.to_s[0..2000]})
80
+ reload
81
+ end
82
+
83
+ def pid=(pid)
84
+ RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid})
85
+ end
86
+
87
+ def running?
88
+ @hasStatus.to_s == 'Running'
89
+ end
90
+
91
+ def completed?
92
+ @hasStatus.to_s == 'Completed'
93
+ end
94
+
95
+ def error?
96
+ @hasStatus.to_s == 'Error'
97
+ end
98
+
99
+ # waits for a task, unless time exceeds or state is no longer running
100
+ def wait_for_completion(dur=0.3)
101
+
102
+ if (@uri.match(@@config[:services]["opentox-task"]))
103
+ due_to_time = (@due_to_time.is_a?(Time) ? @due_to_time : Time.parse(@due_to_time))
104
+ running_time = due_to_time - (@date.is_a?(Time) ? @date : Time.parse(@date))
105
+ else
106
+ # the date of the external task cannot be trusted, offest to local time might be to big
107
+ due_to_time = Time.new + EXTERNAL_TASK_MAX_DURATION
108
+ running_time = EXTERNAL_TASK_MAX_DURATION
109
+ end
110
+ LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s
111
+
112
+ while self.running?
113
+ sleep dur
114
+ reload
115
+ check_state
116
+ if (Time.new > due_to_time)
117
+ raise "max wait time exceeded ("+running_time.to_s+"sec), task: '"+@uri.to_s+"'"
118
+ end
119
+ end
120
+
121
+ LOGGER.debug "Task '"+@hasStatus+"': "+@uri.to_s+", Result: "+@resultURI.to_s
122
+ end
123
+
124
+ def check_state
125
+ begin
126
+ raise "illegal task state, task is completed, resultURI is no URI: '"+@resultURI.to_s+
127
+ "'" unless @resultURI and Utils.is_uri?(@resultURI) if completed?
128
+
129
+ if @http_code == 202
130
+ raise "illegal task state, code is 202, but hasStatus is not Running: '"+@hasStatus+"'" unless running?
131
+ elsif @http_code == 201
132
+ raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@hasStatus+"'" unless completed?
133
+ raise "illegal task state, code is 201, resultURI is no task-URI: '"+@resultURI.to_s+
134
+ "'" unless @resultURI and Utils.task_uri?(@resultURI)
135
+ end
136
+ rescue => ex
137
+ RestClientWrapper.raise_uri_error(ex.message, @uri)
138
+ end
139
+ end
140
+
141
+ # returns the task uri
142
+ # catches halts and exceptions, task state is set to error then
143
+ def self.as_task( title, creator, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil )
144
+ #return yield nil
145
+
146
+ params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description }
147
+ task = OpenTox::Task.create(params)
148
+ task_pid = Spork.spork(:logger => LOGGER) do
149
+ LOGGER.debug "Task #{task.uri} started #{Time.now}"
150
+ $self_task = task
151
+
152
+ begin
153
+ result = catch(:halt) do
154
+ yield task
155
+ end
156
+ # catching halt, set task state to error
157
+ if result && result.is_a?(Array) && result.size==2 && result[0]>202
158
+ LOGGER.error "task was halted: "+result.inspect
159
+ task.error(result[1])
160
+ return
161
+ end
162
+ LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s
163
+ task.completed(result)
164
+ rescue => ex
165
+ LOGGER.error "task failed: "+ex.message
166
+ LOGGER.error ": "+ex.backtrace.join("\n")
167
+ task.error(ex.message)
168
+ end
169
+ end
170
+ task.pid = task_pid
171
+ LOGGER.debug "Started task: "+task.uri.to_s
172
+ task.uri
173
+ end
174
+ end
175
+
176
+ end
@@ -0,0 +1,41 @@
1
+ # Example configuration for OpenTox, please adjust to your settings
2
+ #
3
+ # Database setup:
4
+ #
5
+ # Example MySql:
6
+ #
7
+ :database:
8
+ :adapter: mysql
9
+ :database: production
10
+ :username: root
11
+ :password: opentox
12
+ :host: localhost
13
+ #
14
+ # Example 1: Using external test services
15
+ #
16
+ # :services:
17
+ # opentox-compound: "http://webservices.in-silico.ch/compound/"
18
+ # opentox-dataset: "http://webservices.in-silico.ch/dataset/"
19
+ # opentox-algorithm: "http://webservices.in-silico.ch/algorithm/"
20
+ # opentox-model: "http://webservices.in-silico.ch/model/"
21
+ # opentox-task: "http://webservices.in-silico.ch/task/"
22
+ # opentox-validation: "http://opentox.informatik.uni-freiburg.de/validation/"
23
+ #
24
+ # Example 2: Using local services
25
+ :base_dir: /home/ist/webservices
26
+ :webserver: passenger
27
+ :services:
28
+ opentox-compound: "http://localhost/compound/"
29
+ opentox-dataset: "http://localhost/dataset/"
30
+ opentox-algorithm: "http://localhost/algorithm/"
31
+ opentox-model: "http://localhost/model/"
32
+ opentox-task: "http://localhost/task/"
33
+ opentox-validation: "http://localhost/validation/"
34
+ #
35
+ # Yaml capable hosts (faster than OWL-DL)
36
+ #
37
+ :yaml_hosts:
38
+ - "localhost"
39
+
40
+ # Uncomment for verbose logging
41
+ # :logger: debug