qm 1.2.3 → 1.2.4
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.
- checksums.yaml +4 -4
- data/lib/qm.rb +37 -36
- data/lib/qm/defs-mongo.rb +81 -58
- data/lib/qm/service.rb +41 -21
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30d506778e43477d1c2ebbf9babd96414aff3eca
|
4
|
+
data.tar.gz: 12f678fe08b9ca4ca9d17eb3da23f4362dc0e63e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 191982624850850cc8b0f75abb127bdf60fc77ebfaf07ca02b310674b63863c3d3565c722f40595ac9b253a087b49a2fe9931212803527130d2c2817d87a33d7
|
7
|
+
data.tar.gz: c35d49308fc123a18e23e4eca4e2443756cc26eb2c63e397dc04251bf5eef35646a4ef6ce9346be2d1bcbc7d1eca763e134eb200c3ba9fbb180fdb031c9b71a5
|
data/lib/qm.rb
CHANGED
@@ -2,46 +2,68 @@
|
|
2
2
|
|
3
3
|
#- qm.rb ~~
|
4
4
|
# ~~ (c) SRW, 12 Apr 2013
|
5
|
-
# ~~ last updated
|
5
|
+
# ~~ last updated 29 Jan 2015
|
6
6
|
|
7
7
|
module QM
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
VERSION = '1.2.4'
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def create_app(options = {})
|
14
|
+
# This function creates and configures a fresh app. This code is separate
|
15
|
+
# from the `launch_service` method's code to allow direct use of a
|
16
|
+
# `QMachineService` instance from within a Rackup file ("config.ru").
|
14
17
|
require 'qm/service'
|
15
18
|
app = Sinatra.new(QMachineService) do
|
16
19
|
configure do
|
17
20
|
convert = lambda do |x|
|
18
21
|
# This converts all keys in a hash to symbols recursively.
|
19
22
|
if x.is_a?(Hash) then
|
20
|
-
x = x.inject({}) do |
|
21
|
-
|
22
|
-
memo
|
23
|
+
x = x.inject({}) do |y, (key, val)|
|
24
|
+
y[key.to_sym] = convert.call(val); y
|
23
25
|
end
|
24
26
|
end
|
25
27
|
return x
|
26
28
|
end
|
27
|
-
|
28
|
-
|
29
|
+
convert.call(options).each_pair do |key, val|
|
30
|
+
# This provides feedback for user-specified options.
|
31
|
+
if settings.qm_options.include?(key) then
|
32
|
+
set(key, val)
|
33
|
+
else
|
34
|
+
STDERR.puts "Unknown option: #{key}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
settings.qm_lazy.each do |key|
|
38
|
+
# Eagerly evaluate the lambdas in `QMachineService` in the
|
39
|
+
# correct scope and store their outputs. This strategy avoids
|
40
|
+
# re-evaluating them for every HTTP request later, of course,
|
41
|
+
# but the main motivation is to avoid endlessly opening new
|
42
|
+
# connections without closing old ones.
|
43
|
+
set(key, settings.send(key))
|
44
|
+
end
|
29
45
|
end
|
30
46
|
end
|
31
47
|
return app
|
32
48
|
end
|
33
49
|
|
34
|
-
def
|
50
|
+
def launch_client(options = {mothership: 'https://api.qmachine.org'})
|
35
51
|
# This function needs documentation.
|
36
52
|
require 'qm/client'
|
37
53
|
return QMachineClient.new(options)
|
38
54
|
end
|
39
55
|
|
40
|
-
def
|
56
|
+
def launch_service(options = {})
|
41
57
|
# This function launches a new app using Unicorn :-)
|
42
58
|
require 'unicorn'
|
43
|
-
app =
|
59
|
+
app = create_app(options)
|
44
60
|
Unicorn::HttpServer.new(app, {
|
61
|
+
before_fork: lambda {|server, worker|
|
62
|
+
# This needs documentation.
|
63
|
+
settings = server.app.settings
|
64
|
+
settings.api_db.close if not settings.api_db.nil?
|
65
|
+
settings.log_db.close if not settings.log_db.nil?
|
66
|
+
},
|
45
67
|
listeners: [
|
46
68
|
app.settings.hostname.to_s + ':' + app.settings.port.to_s
|
47
69
|
],
|
@@ -52,32 +74,11 @@ module QM
|
|
52
74
|
return
|
53
75
|
end
|
54
76
|
|
55
|
-
|
56
|
-
def self::launch_service(options = {})
|
57
|
-
# This function launches a new app using Puma. I would prefer to use Puma
|
58
|
-
# instead of Unicorn in the future, in order to support as many of the
|
59
|
-
# different Ruby platforms as possible, but that's not the main priority
|
60
|
-
# for this "teaching version" anyway. Puma will teach *me* a lot about
|
61
|
-
# concurrency in the meantime :-)
|
62
|
-
require 'puma'
|
63
|
-
app = self::create_app(options)
|
64
|
-
server = Puma::Server.new(app)
|
65
|
-
server.add_tcp_listener(app.settings.hostname, app.settings.port)
|
66
|
-
server.min_threads = 1
|
67
|
-
server.max_threads = 16
|
68
|
-
puts "QM up -> http://#{app.settings.hostname}:#{app.settings.port} ..."
|
69
|
-
server.run.join
|
70
|
-
return
|
71
|
-
end
|
72
|
-
=end
|
73
|
-
|
74
|
-
def self::version()
|
77
|
+
def version()
|
75
78
|
# This function exists because it exists in the Node.js version.
|
76
79
|
return VERSION
|
77
80
|
end
|
78
81
|
|
79
|
-
VERSION = '1.2.3'
|
80
|
-
|
81
82
|
end
|
82
83
|
|
83
84
|
#- vim:set syntax=ruby:
|
data/lib/qm/defs-mongo.rb
CHANGED
@@ -1,66 +1,83 @@
|
|
1
1
|
#- Ruby source code
|
2
2
|
|
3
3
|
#- defs-mongo.rb ~~
|
4
|
+
#
|
5
|
+
# See http://www.sinatrarb.com/extensions.html.
|
6
|
+
#
|
4
7
|
# ~~ (c) SRW, 16 Jul 2014
|
5
|
-
# ~~ last updated
|
8
|
+
# ~~ last updated 29 Jan 2015
|
6
9
|
|
7
10
|
require 'json'
|
8
11
|
require 'mongo'
|
9
|
-
require 'sinatra/base'
|
10
12
|
|
11
|
-
module
|
13
|
+
module QM
|
12
14
|
|
13
|
-
|
15
|
+
class MongoStore
|
14
16
|
|
15
|
-
def
|
16
|
-
# This
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
key: Mongo::ASCENDING
|
21
|
-
}, {
|
22
|
-
unique: true
|
23
|
-
})
|
24
|
-
db.collection('avars').ensure_index('exp_date', {
|
25
|
-
expireAfterSeconds: 0
|
26
|
-
})
|
27
|
-
return db
|
17
|
+
def close()
|
18
|
+
# This function needs documentation.
|
19
|
+
@api_db.connection.close if @api_db.respond_to?('connection')
|
20
|
+
@log_db.connection.close if @log_db.respond_to?('connection')
|
21
|
+
return
|
28
22
|
end
|
29
23
|
|
30
|
-
def
|
31
|
-
# This
|
32
|
-
if opts.has_key?(:mongo) then
|
33
|
-
|
24
|
+
def connect_api_store(opts = {})
|
25
|
+
# This function needs documentation.
|
26
|
+
if (opts.has_key?(:mongo)) then
|
27
|
+
@api_db ||= Mongo::MongoClient.from_uri(opts[:mongo]).db
|
28
|
+
@api_db.collection('avars').ensure_index({
|
29
|
+
box: Mongo::ASCENDING,
|
30
|
+
key: Mongo::ASCENDING
|
31
|
+
}, {
|
32
|
+
unique: true
|
33
|
+
})
|
34
|
+
@api_db.collection('avars').ensure_index('exp_date', {
|
35
|
+
expireAfterSeconds: 0
|
36
|
+
})
|
34
37
|
end
|
38
|
+
return @api_db
|
35
39
|
end
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
def connect_log_store(opts = {})
|
42
|
+
# This function needs documentation.
|
43
|
+
if (opts.has_key?(:mongo)) then
|
44
|
+
@log_db ||= Mongo::MongoClient.from_uri(opts[:mongo]).db
|
45
|
+
end
|
46
|
+
return @log_db
|
47
|
+
end
|
40
48
|
|
41
49
|
def get_avar(params)
|
42
|
-
# This helper function
|
43
|
-
|
50
|
+
# This helper function retrieves an avar's representation if it
|
51
|
+
# exists, and it also updates the "expiration date" of the avar in
|
52
|
+
# the database so that data still being used for computations will
|
53
|
+
# not be removed.
|
54
|
+
x = @api_db.collection('avars').find_and_modify({
|
55
|
+
fields: {
|
56
|
+
_id: 0,
|
57
|
+
body: 1
|
58
|
+
},
|
44
59
|
query: {
|
45
60
|
box: params[0],
|
46
61
|
key: params[1]
|
47
62
|
},
|
48
63
|
update: {
|
64
|
+
# NOTE: The hash below must use `=>` (not `:`) in JRuby, as
|
65
|
+
# of version 1.7.18, but QM won't be supporting JRuby anyway
|
66
|
+
# until (a.) JRuby 9000 is stable and (b.) I understand Puma.
|
49
67
|
'$set': {
|
50
|
-
exp_date: Time.now + settings.avar_ttl
|
68
|
+
exp_date: Time.now + @settings.avar_ttl
|
51
69
|
}
|
52
70
|
},
|
53
|
-
fields: {
|
54
|
-
_id: 0,
|
55
|
-
body: 1
|
56
|
-
},
|
57
71
|
upsert: false
|
58
72
|
})
|
59
73
|
return (x.nil?) ? '{}' : x['body']
|
60
74
|
end
|
61
75
|
|
62
76
|
def get_list(params)
|
63
|
-
# This helper function
|
77
|
+
# This helper function retrieves a list of "key" properties for avars
|
78
|
+
# in the database that have a "status" property, because those are
|
79
|
+
# assumed to represent task descriptions. The function returns the
|
80
|
+
# list as a stringified JSON array in which order is not important.
|
64
81
|
opts = {
|
65
82
|
fields: {
|
66
83
|
_id: 0,
|
@@ -69,59 +86,65 @@ module Sinatra
|
|
69
86
|
}
|
70
87
|
query = {
|
71
88
|
box: params[0],
|
89
|
+
exp_date: {
|
90
|
+
'$gt': Time.now
|
91
|
+
},
|
72
92
|
status: params[1]
|
73
93
|
}
|
74
94
|
x = []
|
75
|
-
|
76
|
-
# This block
|
95
|
+
@api_db.collection('avars').find(query, opts).each do |doc|
|
96
|
+
# This block appends each task's key to a running list, but the
|
97
|
+
# the order in which the keys are added is *not* sorted.
|
77
98
|
x.push(doc['key'])
|
78
99
|
end
|
79
100
|
return (x.length == 0) ? '[]' : x.to_json
|
80
101
|
end
|
81
102
|
|
103
|
+
def initialize(opts = {})
|
104
|
+
# This constructor function needs documentation.
|
105
|
+
@settings = opts
|
106
|
+
end
|
107
|
+
|
108
|
+
def log(request)
|
109
|
+
# This helper function inserts a new document into MongoDB after each
|
110
|
+
# request. Eventually, this function will be replaced by one that
|
111
|
+
# delegates to a custom `log` function like the Node.js version.
|
112
|
+
@log_db.collection('traffic').insert({
|
113
|
+
host: request.host,
|
114
|
+
method: request.request_method,
|
115
|
+
timestamp: Time.now,
|
116
|
+
url: request.fullpath
|
117
|
+
}, {
|
118
|
+
w: 0
|
119
|
+
})
|
120
|
+
return
|
121
|
+
end
|
122
|
+
|
82
123
|
def set_avar(params)
|
83
|
-
# This helper function
|
124
|
+
# This helper function writes an avar to the database by "upserting"
|
125
|
+
# a Mongo document that represents it.
|
84
126
|
doc = {
|
85
127
|
body: params.last,
|
86
128
|
box: params[0],
|
87
|
-
exp_date: Time.now + settings.avar_ttl,
|
129
|
+
exp_date: Time.now + @settings.avar_ttl,
|
88
130
|
key: params[1]
|
89
131
|
}
|
90
132
|
doc['status'] = params[2] if params.length == 4
|
91
133
|
opts = {
|
92
134
|
multi: false,
|
93
|
-
upsert: true
|
135
|
+
upsert: true,
|
136
|
+
w: 1
|
94
137
|
}
|
95
138
|
query = {
|
96
139
|
box: params[0],
|
97
140
|
key: params[1]
|
98
141
|
}
|
99
|
-
|
100
|
-
return
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
module MongoLogDefs
|
106
|
-
|
107
|
-
def log_to_db()
|
108
|
-
# This helper function needs documentation.
|
109
|
-
settings.log_db.collection('traffic').insert({
|
110
|
-
host: request.host,
|
111
|
-
ip: request.ip,
|
112
|
-
method: request.request_method,
|
113
|
-
status_code: response.status,
|
114
|
-
timestamp: Time.now,
|
115
|
-
url: request.fullpath
|
116
|
-
})
|
142
|
+
@api_db.collection('avars').update(query, doc, opts)
|
117
143
|
return
|
118
144
|
end
|
119
145
|
|
120
146
|
end
|
121
147
|
|
122
|
-
helpers MongoAPIDefs, MongoLogDefs
|
123
|
-
register MongoConnect
|
124
|
-
|
125
148
|
end
|
126
149
|
|
127
150
|
#- vim:set syntax=ruby:
|
data/lib/qm/service.rb
CHANGED
@@ -16,26 +16,36 @@
|
|
16
16
|
# of a 'box', 'key', or 'status' value.
|
17
17
|
#
|
18
18
|
# ~~ (c) SRW, 24 Apr 2013
|
19
|
-
# ~~ last updated
|
19
|
+
# ~~ last updated 29 Jan 2015
|
20
20
|
|
21
|
-
require 'qm/
|
21
|
+
require 'qm/storage'
|
22
22
|
require 'sinatra/base'
|
23
23
|
require 'sinatra/cross_origin'
|
24
24
|
|
25
25
|
class QMachineService < Sinatra::Base
|
26
26
|
|
27
|
-
register Sinatra::CrossOrigin
|
28
|
-
register Sinatra::MongoConnect
|
27
|
+
register Sinatra::CrossOrigin, QM::StorageConnector
|
29
28
|
|
30
29
|
configure do
|
31
30
|
|
32
|
-
#
|
31
|
+
# MIME types that may be missing
|
33
32
|
|
34
|
-
|
33
|
+
mime_type({
|
34
|
+
webapp: 'application/x-web-app-manifest+json'
|
35
|
+
})
|
35
36
|
|
36
|
-
# QMachine
|
37
|
+
# QMachine definitions
|
37
38
|
|
38
|
-
|
39
|
+
qm_lazy = {
|
40
|
+
api_db: lambda { connect_api_store },
|
41
|
+
bind: lambda { settings.hostname },
|
42
|
+
log_db: lambda { connect_log_store },
|
43
|
+
logging: lambda { settings.log_db.nil? },
|
44
|
+
static: lambda { settings.enable_web_server }
|
45
|
+
}
|
46
|
+
|
47
|
+
qm_options = {
|
48
|
+
avar_ttl: 86400, # seconds (24 * 60 * 60 = 1 day)
|
39
49
|
enable_api_server: false,
|
40
50
|
enable_cors: false,
|
41
51
|
enable_web_server: false,
|
@@ -46,22 +56,25 @@ class QMachineService < Sinatra::Base
|
|
46
56
|
public_folder: 'public',
|
47
57
|
trafficlog_storage: {},
|
48
58
|
worker_procs: 1
|
59
|
+
}
|
49
60
|
|
50
|
-
#
|
61
|
+
# Save the configuration :-)
|
51
62
|
|
52
|
-
|
63
|
+
set(qm_lazy.merge(qm_options).merge({
|
64
|
+
|
65
|
+
# The first two entries here are needed by `QM.create_app`.
|
66
|
+
|
67
|
+
qm_lazy: qm_lazy.keys,
|
68
|
+
qm_options: qm_options.keys,
|
69
|
+
|
70
|
+
# The rest are Rack / Sinatra mappings.
|
53
71
|
|
54
|
-
set api_db: lambda { connect_api_store },
|
55
|
-
bind: lambda { settings.hostname },
|
56
|
-
log_db: lambda { connect_log_store },
|
57
|
-
logging: lambda { settings.log_db.nil? },
|
58
72
|
raise_errors: false,
|
59
73
|
run: false,
|
60
74
|
show_exceptions: false,
|
61
|
-
static: lambda { settings.enable_web_server },
|
62
75
|
x_cascade: false
|
63
76
|
|
64
|
-
|
77
|
+
}))
|
65
78
|
|
66
79
|
end
|
67
80
|
|
@@ -88,6 +101,11 @@ class QMachineService < Sinatra::Base
|
|
88
101
|
halt [444, headers, ['']]
|
89
102
|
end
|
90
103
|
|
104
|
+
def log_to_db()
|
105
|
+
# This helper function needs documentation.
|
106
|
+
settings.log_db.log(request)
|
107
|
+
end
|
108
|
+
|
91
109
|
end
|
92
110
|
|
93
111
|
not_found do
|
@@ -98,7 +116,9 @@ class QMachineService < Sinatra::Base
|
|
98
116
|
# Filter definitions
|
99
117
|
|
100
118
|
after do
|
101
|
-
|
119
|
+
# After every successful request, if logging to stdout has been disabled,
|
120
|
+
# write a new entry into the traffic log database.
|
121
|
+
log_to_db unless response.status == 444 or settings.logging == true
|
102
122
|
end
|
103
123
|
|
104
124
|
before '/:version/:box' do
|
@@ -122,10 +142,10 @@ class QMachineService < Sinatra::Base
|
|
122
142
|
# such as when checking for new tasks to run or downloading results.
|
123
143
|
if @key.is_a?(String) then
|
124
144
|
# This arm runs when a client requests the value of a specific avar.
|
125
|
-
y = get_avar([@box, @key])
|
145
|
+
y = settings.api_db.get_avar([@box, @key])
|
126
146
|
else
|
127
147
|
# This arm runs when a client requests a task queue.
|
128
|
-
y = get_list([@box, @status])
|
148
|
+
y = settings.api_db.get_list([@box, @status])
|
129
149
|
end
|
130
150
|
return [200, {'Content-Type' => 'application/json'}, [y]]
|
131
151
|
end
|
@@ -145,10 +165,10 @@ class QMachineService < Sinatra::Base
|
|
145
165
|
# This arm runs only when a client writes an avar which represents a
|
146
166
|
# task description.
|
147
167
|
hang_up unless x['status'].match(/^[\w\-]+$/)
|
148
|
-
set_avar([@box, @key, x['status'], body])
|
168
|
+
settings.api_db.set_avar([@box, @key, x['status'], body])
|
149
169
|
else
|
150
170
|
# This arm runs when a client is writing a "regular avar".
|
151
|
-
set_avar([@box, @key, body])
|
171
|
+
settings.api_db.set_avar([@box, @key, body])
|
152
172
|
end
|
153
173
|
return [201, {'Content-Type' => 'text/plain'}, ['']]
|
154
174
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Wilkinson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bson_ext
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.12.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.12.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
61
|
+
version: 1.12.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - '='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
68
|
+
version: 1.12.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: sinatra
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|