sequenceserver 1.1.0.beta2 → 1.1.0.beta3
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.
Potentially problematic release.
This version of sequenceserver might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +16 -5
- data/README.md +3 -0
- data/lib/sequenceserver/api_errors.rb +67 -0
- data/lib/sequenceserver/blast/constants.rb +1 -1
- data/lib/sequenceserver/blast/formatter.rb +4 -2
- data/lib/sequenceserver/blast/job.rb +17 -62
- data/lib/sequenceserver/blast/report.rb +15 -2
- data/lib/sequenceserver/blast.rb +1 -0
- data/lib/sequenceserver/exceptions.rb +2 -3
- data/lib/sequenceserver/job.rb +72 -44
- data/lib/sequenceserver/routes.rb +29 -25
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +32 -20
- data/public/js/errormodal.js +55 -0
- data/public/js/report.js +6 -2
- data/public/js/sequenceserver.js +0 -12
- data/public/sequenceserver-report.min.js +15 -15
- data/public/sequenceserver-search.min.js +3 -3
- data/sequenceserver.gemspec +2 -1
- data/spec/capybara_spec.rb +120 -21
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhd +8 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhi +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhr +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nin +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nog +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsd +16 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsi +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsq +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.txt +8 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phd +9140 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phi +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phr +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pin +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pog +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psd +18280 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psi +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psq +0 -0
- data/spec/database/sample/proteins/uniprot/URL +1 -0
- data/spec/nucleotide_query.fa +21 -0
- data/spec/protein_query.fa +21 -0
- data/spec/routes_spec.rb +2 -0
- data/spec/sample_reports/blastn_sample/job.yaml +1 -0
- data/spec/sample_reports/blastn_sample/{rfile → stdout} +0 -0
- data/spec/sample_reports/blastp_sample/job.yaml +1 -0
- data/spec/sample_reports/blastp_sample/{rfile → stdout} +0 -0
- data/spec/sample_reports/blastx_sample/job.yaml +1 -0
- data/spec/sample_reports/blastx_sample/{rfile → stdout} +0 -0
- data/spec/sample_reports/no_hits_sample/job.yaml +1 -0
- data/spec/sample_reports/no_hits_sample/{rfile → stdout} +0 -0
- data/spec/sample_reports/tblastn_sample/job.yaml +1 -0
- data/spec/sample_reports/tblastn_sample/{rfile → stdout} +0 -0
- data/spec/sample_reports/tblastx_sample/job.yaml +1 -0
- data/spec/sample_reports/tblastx_sample/{rfile → stdout} +0 -0
- data/spec/sample_reports/with_hits_sample/job.yaml +1 -0
- data/spec/sample_reports/with_hits_sample/{rfile → stdout} +0 -0
- data/spec/sequenceserver_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -16
- data/views/layout.erb +0 -45
- metadata +55 -16
- data/lib/sequenceserver/blast/exceptions.rb +0 -27
- data/views/400.erb +0 -29
- data/views/500.erb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e60bdefc921484208426a507ff32229e5945b1b
|
4
|
+
data.tar.gz: 85a162161cae976cc790ae3fed7b06ab81f11786
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4511c2651001127d25f0b319984ed4cbc5b4e4d6dccde42823b1c33fd14a99e86045d8416da1d8e514e6825df44933359d32eafe1167e0bdb30243d2017a295
|
7
|
+
data.tar.gz: 3917c29dfb8be319b92029295f3996338195aa3bab09a176c625b22e1f7b7fe56588aedf8d16b8724aa07acdfbe35c1c3982a3f2a3bbf47d165745d11cef4523
|
data/.travis.yml
CHANGED
@@ -6,7 +6,7 @@ rvm:
|
|
6
6
|
- 2.5
|
7
7
|
|
8
8
|
addons:
|
9
|
-
|
9
|
+
sauce_connect: true
|
10
10
|
|
11
11
|
branches:
|
12
12
|
only:
|
@@ -14,15 +14,26 @@ branches:
|
|
14
14
|
- master
|
15
15
|
|
16
16
|
install:
|
17
|
-
- wget -c "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux64.tar.gz"
|
18
|
-
- mkdir geckodriver && tar xvf geckodriver-*.tar.gz -C geckodriver
|
19
|
-
- export PATH="$PWD/geckodriver:$PATH"
|
20
17
|
- wget -c "ftp://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/2.6.0/ncbi-blast-2.6.0+-x64-linux.tar.gz"
|
21
18
|
- tar xvf ncbi-blast-*.tar.gz
|
22
19
|
- gem install bundler && bundle
|
23
20
|
- bundle exec bin/sequenceserver -s -b ncbi-blast-2.6.0+/bin -d spec/database/sample
|
24
21
|
|
25
|
-
|
22
|
+
before_script:
|
23
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter && chmod +x ./cc-test-reporter
|
24
|
+
- ./cc-test-reporter before-build
|
25
|
+
script:
|
26
|
+
- bundle exec rspec --exclude-pattern 'capybara*' # unit tests
|
27
|
+
- platform='macOS 10.13' browserName=safari browserVersion=11 bundle exec rspec spec/capybara_spec.rb
|
28
|
+
- platform='macOS 10.13' browserName=chrome browserVersion=65 bundle exec rspec spec/capybara_spec.rb
|
29
|
+
- platform='macOS 10.13' browserName=firefox browserVersion=59 bundle exec rspec spec/capybara_spec.rb
|
30
|
+
- platform='Windows 10' browserName=microsoftedge browserVersion='16.16299' bundle exec rspec spec/capybara_spec.rb
|
31
|
+
- platform='Windows 10' browserName=chrome browserVersion=65 bundle exec rspec spec/capybara_spec.rb
|
32
|
+
- platform='Windows 10' browserName=firefox browserVersion=59 bundle exec rspec spec/capybara_spec.rb
|
33
|
+
- platform='Linux' browserName=chrome browserVersion=48 bundle exec rspec spec/capybara_spec.rb
|
34
|
+
- platform='Linux' browserName=firefox browserVersion=45 bundle exec rspec spec/capybara_spec.rb
|
35
|
+
after_script:
|
36
|
+
- ./cc-test-reporter after-build # --exit-code $TRAVIS_TEST_RESULT
|
26
37
|
|
27
38
|
cache:
|
28
39
|
directories:
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
[](https://travis-ci.org/wurmlab/sequenceserver)
|
2
|
+
[](https://saucelabs.com/u/yeban)
|
2
3
|
[](https://codeclimate.com/github/wurmlab/sequenceserver)
|
3
4
|
[](https://codeclimate.com/github/wurmlab/sequenceserver)
|
4
5
|
[](http://rubygems.org/gems/sequenceserver)
|
@@ -6,6 +7,8 @@
|
|
6
7
|
|
7
8
|
[](https://gitter.im/wurmlab/sequenceserver)
|
8
9
|
|
10
|
+
[](https://saucelabs.com/u/yeban)
|
11
|
+
|
9
12
|
# SequenceServer - BLAST searching made easy!
|
10
13
|
|
11
14
|
SequenceServer lets you rapidly set up a BLAST+ server with an intuitive user
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module SequenceServer
|
2
|
+
|
3
|
+
# API errors have an http status, title, message, and additional information
|
4
|
+
# like stacktrace or information from program output.
|
5
|
+
class APIError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Job not found (404).
|
9
|
+
class NotFound < APIError
|
10
|
+
def http_status
|
11
|
+
404
|
12
|
+
end
|
13
|
+
|
14
|
+
def title
|
15
|
+
'Job not found'
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
'The requested job could not be found'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Errors caused due to incorrect user input.
|
24
|
+
class InputError < APIError
|
25
|
+
def initialize(more_info)
|
26
|
+
@more_info = more_info
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def http_status
|
31
|
+
400
|
32
|
+
end
|
33
|
+
|
34
|
+
def title
|
35
|
+
'Input error'
|
36
|
+
end
|
37
|
+
|
38
|
+
def message
|
39
|
+
<<MSG
|
40
|
+
Looks like there's a problem with one of the query sequences, selected
|
41
|
+
databases, or advanced parameters.
|
42
|
+
MSG
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :more_info
|
46
|
+
end
|
47
|
+
|
48
|
+
# Errors caused by everything other than invalid user input.
|
49
|
+
class SystemError < APIError
|
50
|
+
def http_status
|
51
|
+
500
|
52
|
+
end
|
53
|
+
|
54
|
+
def title
|
55
|
+
'Job failed'
|
56
|
+
end
|
57
|
+
|
58
|
+
def message
|
59
|
+
<<MSG
|
60
|
+
Sorry BLAST failed - please try again. If this message persists, there is a
|
61
|
+
problem with the server. In this case, please report the bug on our
|
62
|
+
<a href="https://github.com/wurmlab/sequenceserver/issues" target="_blank">
|
63
|
+
issue tracker</a>.
|
64
|
+
MSG
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -24,7 +24,7 @@ module SequenceServer
|
|
24
24
|
attr_reader :format, :mime, :specifiers
|
25
25
|
|
26
26
|
def file
|
27
|
-
@file
|
27
|
+
@file ||= File.join(File.dirname(archive_file), filename)
|
28
28
|
end
|
29
29
|
|
30
30
|
def filename
|
@@ -41,12 +41,14 @@ module SequenceServer
|
|
41
41
|
" -outfmt '#{format} #{specifiers}'" \
|
42
42
|
" -out '#{file}'"
|
43
43
|
sys(command, path: config[:bin], dir: DOTDIR)
|
44
|
+
rescue CommandFailed => e
|
45
|
+
fail SystemError, e.stderr
|
44
46
|
end
|
45
47
|
|
46
48
|
def validate
|
47
49
|
return true if archive_file && format &&
|
48
50
|
File.exist?(archive_file)
|
49
|
-
fail
|
51
|
+
fail InputError, <<MSG
|
50
52
|
Incorrect request parameters. Please ensure that requested file name is
|
51
53
|
correct and the file type is either xml or tsv.
|
52
54
|
MSG
|
@@ -1,17 +1,10 @@
|
|
1
|
-
require 'sequenceserver/pool'
|
2
1
|
require 'sequenceserver/job'
|
3
2
|
|
4
|
-
require_relative 'exceptions'
|
5
|
-
require_relative 'constants'
|
6
|
-
|
7
3
|
module SequenceServer
|
8
4
|
|
9
5
|
# BLAST module.
|
10
6
|
module BLAST
|
11
7
|
# Extends SequenceServer::Job to describe a BLAST job.
|
12
|
-
#
|
13
|
-
# `BLAST::ArgumentError` and `BLAST::RuntimeError` signal errors
|
14
|
-
# encountered when attempting a BLAST search.
|
15
8
|
class Job < Job
|
16
9
|
|
17
10
|
def initialize(params)
|
@@ -25,60 +18,23 @@ module SequenceServer
|
|
25
18
|
end
|
26
19
|
end
|
27
20
|
|
28
|
-
|
29
21
|
attr_reader :advanced_params
|
22
|
+
|
30
23
|
# :nodoc:
|
31
|
-
# Attributes used by us.
|
32
|
-
#
|
33
|
-
# Should be considered private.
|
24
|
+
# Attributes used by us - should be considered private.
|
34
25
|
attr_reader :method, :qfile, :databases, :options
|
35
26
|
|
36
|
-
#
|
37
|
-
#
|
38
|
-
def
|
39
|
-
command
|
40
|
-
|
41
|
-
|
42
|
-
sys(command, stdout: rfile, stderr: efile, path: config[:bin])
|
43
|
-
done!
|
44
|
-
rescue CommandFailed => e
|
45
|
-
# Capture error.
|
46
|
-
status = e.exitstatus
|
47
|
-
case status
|
48
|
-
when 1 # error in query sequence or options; see [1]
|
49
|
-
efile.open
|
50
|
-
|
51
|
-
# Most of the time BLAST+ generates a verbose error message with
|
52
|
-
# details we don't require. So we parse out the relevant lines.
|
53
|
-
error = efile.each_line do |l|
|
54
|
-
break Regexp.last_match[1] if l.match(ERROR_LINE)
|
55
|
-
end
|
56
|
-
|
57
|
-
# But sometimes BLAST+ returns the exact/ relevant error message.
|
58
|
-
# Trying to parse such messages returns nil, and we use the error
|
59
|
-
# message from BLAST+ as it is.
|
60
|
-
error = efile.rewind && efile.read unless error.is_a? String
|
61
|
-
|
62
|
-
efile.close
|
63
|
-
fail ArgumentError, error
|
64
|
-
when 2, 3, 4, 255 # see [1]
|
65
|
-
efile.open
|
66
|
-
error = efile.read
|
67
|
-
efile.close
|
68
|
-
fail RuntimeError.new(status, error)
|
69
|
-
end
|
70
|
-
|
71
|
-
success!
|
72
|
-
end
|
73
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
74
|
-
# rubocop:enable Metrics/MethodLength
|
75
|
-
|
76
|
-
def rfile
|
77
|
-
File.join(dir, 'rfile')
|
27
|
+
# Returns the command that will be executed. Job super class takes care
|
28
|
+
# of actual execution.
|
29
|
+
def command
|
30
|
+
@command ||= "#{method} -db '#{databases.map(&:name).join(' ')}'" \
|
31
|
+
" -query '#{qfile}' #{options}"
|
78
32
|
end
|
79
33
|
|
80
|
-
|
81
|
-
|
34
|
+
# BLAST's exit status is not definitive of success or error, so we
|
35
|
+
# override success? to define custom criteria. :TODO:
|
36
|
+
def success?
|
37
|
+
super
|
82
38
|
end
|
83
39
|
|
84
40
|
private
|
@@ -114,38 +70,37 @@ module SequenceServer
|
|
114
70
|
|
115
71
|
def validate_method(method)
|
116
72
|
return true if ALGORITHMS.include? method
|
117
|
-
fail
|
73
|
+
fail InputError, 'BLAST algorithm should be one of:' \
|
118
74
|
" #{ALGORITHMS.join(', ')}."
|
119
75
|
end
|
120
76
|
|
121
77
|
def validate_sequences(sequences)
|
122
78
|
return true if sequences.is_a?(String) && !sequences.empty?
|
123
|
-
fail
|
79
|
+
fail InputError, 'Sequences should be a non-empty string.'
|
124
80
|
end
|
125
81
|
|
126
82
|
def validate_databases(database_ids)
|
127
83
|
ids = Database.ids
|
128
84
|
return true if database_ids.is_a?(Array) && !database_ids.empty? &&
|
129
85
|
(ids & database_ids).length == database_ids.length
|
130
|
-
fail
|
131
|
-
" #{ids.join("\n")}."
|
86
|
+
fail InputError, "Database id should be one of: #{ids.join("\n")}."
|
132
87
|
end
|
133
88
|
|
134
89
|
# Advanced options are specified by the user. Here they are checked for
|
135
90
|
# interference with SequenceServer operations.
|
136
91
|
#
|
137
|
-
# Raise
|
92
|
+
# Raise InputError if an error has occurred.
|
138
93
|
def validate_options(options)
|
139
94
|
return true if !options || (options.is_a?(String) &&
|
140
95
|
options.strip.empty?)
|
141
96
|
|
142
97
|
unless allowed_chars.match(options)
|
143
|
-
fail
|
98
|
+
fail InputError, 'Invalid characters detected in options.'
|
144
99
|
end
|
145
100
|
|
146
101
|
if disallowed_options.match(options)
|
147
102
|
failedopt = Regexp.last_match[0]
|
148
|
-
fail
|
103
|
+
fail InputError, "Option \"#{failedopt}\" is prohibited."
|
149
104
|
end
|
150
105
|
|
151
106
|
true
|
@@ -43,14 +43,27 @@ module SequenceServer
|
|
43
43
|
|
44
44
|
# Generate report.
|
45
45
|
def generate
|
46
|
-
|
47
|
-
|
46
|
+
assert_job_completed_successfully
|
47
|
+
xml_ir = parse_xml File.read(Formatter.run(job.stdout, 'xml').file)
|
48
|
+
tsv_ir = parse_tsv File.read(Formatter.run(job.stdout, 'custom_tsv').file)
|
48
49
|
extract_program_info xml_ir
|
49
50
|
extract_params xml_ir
|
50
51
|
extract_stats xml_ir
|
51
52
|
extract_queries xml_ir, tsv_ir
|
52
53
|
end
|
53
54
|
|
55
|
+
def assert_job_completed_successfully
|
56
|
+
return true if job.success?
|
57
|
+
|
58
|
+
stderr = File.readlines(job.stderr)
|
59
|
+
if job.exitstatus == 1 # error in query sequence or options; see [1]
|
60
|
+
error = stderr.grep(ERROR_LINE)
|
61
|
+
error = stderr if error.empty?
|
62
|
+
fail InputError, error.join
|
63
|
+
end
|
64
|
+
fail SystemError, stderr.join
|
65
|
+
end
|
66
|
+
|
54
67
|
# Make program name and program name + version available via `program`
|
55
68
|
# and `program_version` attributes.
|
56
69
|
def extract_program_info(ir)
|
data/lib/sequenceserver/blast.rb
CHANGED
@@ -148,13 +148,12 @@ MSG
|
|
148
148
|
# Raised if the 'sys' method could not successfully execute a shell command.
|
149
149
|
class CommandFailed < StandardError
|
150
150
|
|
151
|
-
def initialize(stdout, stderr
|
151
|
+
def initialize(exitstatus, stdout: nil, stderr: nil)
|
152
|
+
@exitstatus = exitstatus
|
152
153
|
@stdout = stdout
|
153
154
|
@stderr = stderr
|
154
|
-
@exitstatus = exitstatus
|
155
155
|
end
|
156
156
|
|
157
157
|
attr_reader :stdout, :stderr, :exitstatus
|
158
|
-
|
159
158
|
end
|
160
159
|
end
|
data/lib/sequenceserver/job.rb
CHANGED
@@ -5,44 +5,46 @@ require 'digest/md5'
|
|
5
5
|
require 'forwardable'
|
6
6
|
require 'securerandom'
|
7
7
|
|
8
|
+
require 'sequenceserver/pool'
|
9
|
+
require 'sequenceserver/api_errors'
|
10
|
+
|
8
11
|
module SequenceServer
|
9
|
-
#
|
12
|
+
# Abstract job super class.
|
13
|
+
#
|
14
|
+
# Provides a simple framework to store job data, execute shell commands
|
15
|
+
# asynchronously and capture stdout, stderr and exit status. Subclasses
|
16
|
+
# must provide a concrete implementation for `command` and may override
|
17
|
+
# any other methods as required.
|
10
18
|
#
|
11
|
-
#
|
12
|
-
#
|
19
|
+
# Global `config` and `logger` object are available as instance methods.
|
20
|
+
#
|
21
|
+
# Singleton methods provide the facility to create and queue a job,
|
22
|
+
# fetch a job or all jobs, and delete a job.
|
13
23
|
class Job
|
14
|
-
UUID_PATTERN = /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/
|
15
|
-
|
16
24
|
class << self
|
17
25
|
# Creates and queues a job. Returns created job object.
|
18
|
-
#
|
19
|
-
# TODO: Implement dynamic dispatch.
|
20
26
|
def create(params)
|
21
|
-
|
27
|
+
job = BLAST::Job.new(params)# TODO: Dynamic dispatch.
|
28
|
+
SequenceServer.pool.queue { job.run }
|
29
|
+
job
|
22
30
|
end
|
23
31
|
|
24
|
-
# Fetches job
|
25
|
-
#
|
26
|
-
# TODO: What if the given job id does not exist?
|
32
|
+
# Fetches job with the given id.
|
27
33
|
def fetch(id)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def all
|
32
|
-
jobdirs = Dir["#{DOTDIR}/*"].select{ |f| f =~ UUID_PATTERN }
|
33
|
-
jobdirs.map{ |fname| fetch File.basename(fname) }
|
34
|
+
job_file = File.join(DOTDIR, id, 'job.yaml')
|
35
|
+
fail NotFound unless File.exist?(job_file)
|
36
|
+
YAML.load_file(job_file)
|
34
37
|
end
|
35
38
|
|
39
|
+
# Deletes job with the given id.
|
36
40
|
def delete(id)
|
37
41
|
FileUtils.rm_r File.join(DOTDIR, id)
|
38
42
|
end
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
SequenceServer.pool.queue { job.run }
|
45
|
-
job
|
44
|
+
# Returns an Array of all jobs.
|
45
|
+
def all
|
46
|
+
Dir["#{DOTDIR}/**/job.yaml"].
|
47
|
+
map { |f| fetch File.basename File.dirname f }
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
@@ -66,32 +68,49 @@ module SequenceServer
|
|
66
68
|
raise e
|
67
69
|
end
|
68
70
|
|
69
|
-
attr_reader :id, :completed_at
|
71
|
+
attr_reader :id, :completed_at, :exitstatus
|
70
72
|
|
71
|
-
#
|
73
|
+
# Shell out and execute the job.
|
72
74
|
#
|
73
|
-
#
|
74
|
-
# call `done!` and `success!`, as appropriate, to indicate if the job is
|
75
|
-
# done and whether it was successful.
|
75
|
+
# NOTE: This method is called asynchronously by thread pool.
|
76
76
|
def run
|
77
|
-
|
77
|
+
sys(command, path: config[:bin], stdout: stdout, stderr: stderr)
|
78
|
+
done!
|
79
|
+
rescue CommandFailed => e
|
80
|
+
done! e.exitstatus
|
78
81
|
end
|
79
82
|
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
+
# Returns shell command that will be executed. Subclass needs to provide a
|
84
|
+
# concrete implementation.
|
85
|
+
def command
|
86
|
+
raise "Not implemented."
|
83
87
|
end
|
84
88
|
|
85
|
-
#
|
89
|
+
# Where will the stdout be written to during execution and read from later.
|
90
|
+
def stdout
|
91
|
+
File.join(dir, 'stdout')
|
92
|
+
end
|
93
|
+
|
94
|
+
# Where will the stderr be written to during execution and read from later.
|
95
|
+
def stderr
|
96
|
+
File.join(dir, 'stderr')
|
97
|
+
end
|
98
|
+
|
99
|
+
# Was the job successful?
|
86
100
|
def success?
|
87
|
-
|
101
|
+
exitstatus == 0
|
102
|
+
end
|
103
|
+
|
104
|
+
# Is the job done? Yes if exitstatus of the job is available. No otherwise.
|
105
|
+
def done?
|
106
|
+
!!@exitstatus
|
88
107
|
end
|
89
108
|
|
90
109
|
private
|
91
110
|
|
92
|
-
#
|
111
|
+
# Saves job object to a YAML file in job directory.
|
93
112
|
def save
|
94
|
-
File.write(
|
113
|
+
File.write(yfile, to_yaml)
|
95
114
|
end
|
96
115
|
|
97
116
|
# Save arbitrary blob of data for this job to a file. Returns absolute path
|
@@ -108,16 +127,20 @@ module SequenceServer
|
|
108
127
|
filename
|
109
128
|
end
|
110
129
|
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
130
|
+
# Retrieve file from job dir with the given name. Raises RuntimeError if
|
131
|
+
# the file can't be found.
|
132
|
+
#
|
133
|
+
# NOTE: Not used.
|
134
|
+
def fetch(key)
|
135
|
+
filename = File.join(dir, key)
|
136
|
+
raise if !File.exist? filename
|
137
|
+
filename
|
116
138
|
end
|
117
139
|
|
118
|
-
# Marks the job as
|
119
|
-
def
|
120
|
-
@
|
140
|
+
# Marks the job as done and save its exitstatus.
|
141
|
+
def done!(status = 0)
|
142
|
+
@completed_at = Time.now
|
143
|
+
@exitstatus = status
|
121
144
|
save
|
122
145
|
end
|
123
146
|
|
@@ -125,5 +148,10 @@ module SequenceServer
|
|
125
148
|
def dir
|
126
149
|
File.join(DOTDIR, id)
|
127
150
|
end
|
151
|
+
|
152
|
+
# Where to write serialised job object.
|
153
|
+
def yfile
|
154
|
+
File.join(dir, 'job.yaml')
|
155
|
+
end
|
128
156
|
end
|
129
157
|
end
|
@@ -12,16 +12,11 @@ module SequenceServer
|
|
12
12
|
disable :method_override
|
13
13
|
|
14
14
|
# Ensure exceptions never leak out of the app. Exceptions raised within
|
15
|
-
# the app must be handled by the app.
|
16
|
-
# blocks to exceptions we know how to handle and attaching to Exception
|
17
|
-
# as fallback.
|
15
|
+
# the app must be handled by the app.
|
18
16
|
disable :show_exceptions, :raise_errors
|
19
17
|
|
20
18
|
# Make it a policy to dump to 'rack.errors' any exception raised by the
|
21
|
-
# app
|
22
|
-
# to always work, Exceptions defined by us should not respond to `code`
|
23
|
-
# or `http_status` methods. Error blocks must explicitly set http
|
24
|
-
# status, if needed, by calling `status` method.
|
19
|
+
# app.
|
25
20
|
enable :dump_errors
|
26
21
|
|
27
22
|
# We don't want Sinatra do setup any loggers for us. We will use our own.
|
@@ -89,8 +84,7 @@ module SequenceServer
|
|
89
84
|
get '/:jid.json' do |jid|
|
90
85
|
job = Job.fetch(jid)
|
91
86
|
halt 202 unless job.done?
|
92
|
-
|
93
|
-
rep.to_json
|
87
|
+
Report.generate(job).to_json
|
94
88
|
end
|
95
89
|
|
96
90
|
# Returns base HTML. Rest happens client-side: polling for and rendering
|
@@ -132,24 +126,34 @@ module SequenceServer
|
|
132
126
|
send_file out.file, :filename => out.filename, :type => out.mime
|
133
127
|
end
|
134
128
|
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
|
139
|
-
|
129
|
+
# Catches any exception raised within the app and returns error message and
|
130
|
+
# the backtrace as response body. If the error class defines `http_status`
|
131
|
+
# instance method, its return value will be used to set HTTP status. HTTP
|
132
|
+
# status is set to 500 otherwise.
|
133
|
+
error 400..500 do
|
140
134
|
error = env['sinatra.error']
|
141
|
-
erb :'400', :layout => nil, :locals => { :error => error }
|
142
|
-
end
|
143
135
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
136
|
+
# All errors will have a message.
|
137
|
+
error_data = {message: error.message}
|
138
|
+
|
139
|
+
# If error object has a title method, use that, or use error class for
|
140
|
+
# title.
|
141
|
+
if error.respond_to? :title
|
142
|
+
error_data[:title] = error.title
|
143
|
+
else
|
144
|
+
error_data[:title] = error.class.to_s
|
145
|
+
end
|
146
|
+
|
147
|
+
# If error object has a more_info method, use that. If the error does
|
148
|
+
# not have more_info and the error is not APIError, use backtrace as
|
149
|
+
# more_info.
|
150
|
+
if error.respond_to? :more_info
|
151
|
+
error_data[:more_info] = error.more_info
|
152
|
+
else
|
153
|
+
error_data[:more_info] = error.backtrace.join("\n")
|
154
|
+
end
|
155
|
+
|
156
|
+
error_data.to_json
|
153
157
|
end
|
154
158
|
end
|
155
159
|
end
|