opentox-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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