oni 0.0.1 → 1.0.0
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 +8 -8
- data/.gitignore +4 -0
- data/.travis.yml +23 -0
- data/.yardopts +12 -0
- data/Gemfile +7 -0
- data/LICENSE +19 -0
- data/README.md +170 -0
- data/Rakefile +13 -0
- data/doc/changelog.md +6 -0
- data/doc/css/common.css +69 -0
- data/examples/github_status.rb +75 -0
- data/jenkins.sh +16 -0
- data/lib/oni.rb +7 -1
- data/lib/oni/configurable.rb +107 -0
- data/lib/oni/daemon.rb +190 -0
- data/lib/oni/daemons/sqs.rb +61 -0
- data/lib/oni/mapper.rb +30 -0
- data/lib/oni/version.rb +1 -1
- data/lib/oni/worker.rb +18 -0
- data/oni.gemspec +30 -0
- data/spec/oni/configurable_spec.rb +56 -0
- data/spec/oni/daemon_spec.rb +154 -0
- data/spec/oni/daemons/sqs_spec.rb +31 -0
- data/spec/oni/mapper_spec.rb +25 -0
- data/spec/oni/worker_spec.rb +33 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/simplecov.rb +12 -0
- data/task/coverage.rake +6 -0
- data/task/doc.rake +4 -0
- data/task/jenkins.rake +2 -0
- data/task/tag.rake +6 -0
- data/task/test.rake +4 -0
- metadata +59 -9
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
Yzg0ZTUxZTdiN2E0YmVkM2M2NGFkYTNiMWY0ZDZiNDVhZDAyMmM1Ng==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MzJiNjliMjhiZjcwZTk4NTkyZjFkZjU4YTI1YjJkMmEyNjQ0Y2EwYw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OTE4MzgzNjMxZDI5YzZhYjVmYWQ2OTUzZWE1MTI5NzY5YzUzNWY0ZjY4MjNl
|
10
|
+
MzhhN2JkOGFjNTEzMzk2ODU0YjlhNTE5YmY2NGQ3MzMxZTI5MDk3YmFjZWU5
|
11
|
+
YjYwZmI5MmU0NGFmNjkwY2RhMGIxMzk1NDUwYzg4OTUwZGFiNTQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NjAzNWExYjhmNWI0ZWQzMjY2NWY0NTMwYjQ1MjZlNjExNDg2NjJlNWY5MmNl
|
14
|
+
ZGY2Y2JiMTExMDgwNGE3ODVlMDJiNjk5NWRmMWM1ZjYwODg4YmQzNWMxYTUz
|
15
|
+
ZDFiNjcxZGM0OTdiYTBhYWZmMjE3MjcxOWVlZmY2MDZmMmMzODc=
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
---
|
2
|
+
script: rake
|
3
|
+
|
4
|
+
rvm:
|
5
|
+
- 1.9.3
|
6
|
+
- 2.0.0
|
7
|
+
- 2.1.0-preview1
|
8
|
+
- jruby
|
9
|
+
- jruby-head
|
10
|
+
- jruby-1.7.4
|
11
|
+
- rbx-2.1.1
|
12
|
+
|
13
|
+
notifications:
|
14
|
+
email:
|
15
|
+
recipients:
|
16
|
+
- development@olery.com
|
17
|
+
email:
|
18
|
+
on_success: change
|
19
|
+
on_failure: change
|
20
|
+
|
21
|
+
branches:
|
22
|
+
only:
|
23
|
+
- master
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013, Olery <http://olery.com/>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# Oni
|
2
|
+
|
3
|
+
* [Design](#design)
|
4
|
+
* [The Daemon](#the-daemon)
|
5
|
+
* [The Mapper](#the-mapper)
|
6
|
+
* [The Worker](#the-worker)
|
7
|
+
* [Requirements](#requirements)
|
8
|
+
* [Installation & Basic Usage](#installation--basic-usage)
|
9
|
+
* [License](#license)
|
10
|
+
|
11
|
+
Oni is a Ruby framework that aims to make it easier to write concurrent daemons
|
12
|
+
using a common code structure. Oni itself does not actually daemonize your
|
13
|
+
code, manage PID files, resources, etc. Instead you should use Oni in
|
14
|
+
combination with other Gems such as [daemon-kit][daemon-kit].
|
15
|
+
|
16
|
+
Oni was built to standardize the structure amongst the different daemon-kit
|
17
|
+
projects used at [Olery][olery]. As time progressed new structures were used
|
18
|
+
for new daemons and the old ones were often left as is.
|
19
|
+
|
20
|
+
Another problem we faced was concurrency. Most daemons were built in a
|
21
|
+
single threaded, single processed manner without an easy place to hook some
|
22
|
+
kind of concurrency model in.
|
23
|
+
|
24
|
+
Oni takes care of these problems by providing the following:
|
25
|
+
|
26
|
+
* A concurrency model in the form of separate worker threads (5 by default).
|
27
|
+
* Clear separation of logic into 3 distinctive parts.
|
28
|
+
* A common structure for your daemon projects.
|
29
|
+
|
30
|
+
Oni assumes developers are somewhat familiar with threading and the potential
|
31
|
+
issues that may arise, it also assumes that your code doesn't leak large
|
32
|
+
amounts of memory over time. Currently there are no plans to include some kind
|
33
|
+
of internal resource management system in Oni, this may change in the future.
|
34
|
+
|
35
|
+
## Design
|
36
|
+
|
37
|
+
To understand the design of Oni we'll first look at the typical work flow of a
|
38
|
+
daemon:
|
39
|
+
|
40
|
+
1. A job gets put in a queue (Amazon SQS).
|
41
|
+
2. Daemon polls queue, takes message.
|
42
|
+
3. Optional message format validation (this was rarely enforced since we had
|
43
|
+
control over the input and assumed it to be correct).
|
44
|
+
4. Work gets offloaded to some extra class, in older designs of our daemons it
|
45
|
+
would happen in the daemon layer directly.
|
46
|
+
5. Optionally the input data would be modified and re-queued into a separate
|
47
|
+
queue. For example, we often pass along data through multiple queues from
|
48
|
+
the start until the very end (e.g. batch IDs).
|
49
|
+
|
50
|
+
Oni tries to make these kind of workflows by breaking them up into 3 different
|
51
|
+
layers:
|
52
|
+
|
53
|
+
1. A daemon layer tasked with receiving and scheduling work.
|
54
|
+
2. A "mapper" tasked with transforming (and validating) input/output for/from
|
55
|
+
the worker. It sits between the daemon and the worker.
|
56
|
+
3. A worker that actually performs a task (asynchronously)
|
57
|
+
|
58
|
+
Each layer would only do the specific thing it should be doing and would
|
59
|
+
offload other work to the next step in the process. The 3 parts are described
|
60
|
+
in detail below.
|
61
|
+
|
62
|
+
### The Daemon
|
63
|
+
|
64
|
+
The daemon layer spawns a number of threads that will each receive and perform
|
65
|
+
work separately. Typically these workers are long running tasks that poll some
|
66
|
+
kind of message queue for jobs to process.
|
67
|
+
|
68
|
+
In initial iterations Oni used a main job dispatcher (running in the main
|
69
|
+
thread) and a separate thread pool for the workers. This proved problematic
|
70
|
+
with message queue setups as it would result in the main thread pulling in all
|
71
|
+
available jobs and then internally queing them again given there weren't enough
|
72
|
+
workers available. This would mean that if the process would crash the messages
|
73
|
+
were lost. As a result of this each worker is started in it's own separate
|
74
|
+
thread.
|
75
|
+
|
76
|
+
Comparing Oni with other framework structures one could see the daemon layer as
|
77
|
+
a controller (in MVC frameworks), it merely dispatches work to the mapper and
|
78
|
+
worker instead of doing everything itself.
|
79
|
+
|
80
|
+
### The Mapper
|
81
|
+
|
82
|
+
The mapper is tasked with two things:
|
83
|
+
|
84
|
+
1. Take the input from the daemon, validate it and transform it into a
|
85
|
+
structure that the worker can understand.
|
86
|
+
2. Take the resulting output, optionally modify it and pass it back to the
|
87
|
+
daemon.
|
88
|
+
|
89
|
+
The input transformation is put in place to ensure that workers only get data
|
90
|
+
that they actually need instead of just receiving the raw message (which may
|
91
|
+
include all kinds of meta data completely useless to a worker). It also ensures
|
92
|
+
that the input is actually correct before ever passing it to a worker.
|
93
|
+
|
94
|
+
A typical thing at Olery is that a job gets scheduled and has to pass through
|
95
|
+
multiple steps (= daemons) before being completed. Some of these daemons would
|
96
|
+
add extra meta-data to the message (e.g. batch IDs, timings, etc) but these
|
97
|
+
aren't strictly required to perform an actual job, thus there's no need to pass
|
98
|
+
it around several layers deep into your codebase.
|
99
|
+
|
100
|
+
In the above case the mapper would take care of validating/scrubbing the input
|
101
|
+
and adding extra meta-data to the output.
|
102
|
+
|
103
|
+
### The Worker
|
104
|
+
|
105
|
+
The worker would perform the actual work and return some kind of output to the
|
106
|
+
daemon. Oni assumes that workers behave reasonably well, currently there's no
|
107
|
+
mechanism in place to deal with memory leaks and the likes. Oni also assumes
|
108
|
+
that developers are somewhat capable of dealing with asynchronous code since
|
109
|
+
all work is performed asynchronously outside of the daemon layer.
|
110
|
+
|
111
|
+
## Requirements
|
112
|
+
|
113
|
+
* Ruby 1.9.3 or newer, preferably an implementation without a GIL such as
|
114
|
+
Rubinius or Jruby.
|
115
|
+
* Basic understanding of threading/concurrent programming
|
116
|
+
|
117
|
+
## Installation & Basic Usage
|
118
|
+
|
119
|
+
Install the Gem:
|
120
|
+
|
121
|
+
gem install oni
|
122
|
+
|
123
|
+
Basic usage of Oni is as following:
|
124
|
+
|
125
|
+
require 'oni'
|
126
|
+
|
127
|
+
class MyWorker < Oni::Worker
|
128
|
+
def initialize(number)
|
129
|
+
@number = number
|
130
|
+
end
|
131
|
+
|
132
|
+
def process
|
133
|
+
return @number * 2
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class MyMapper < Oni::Mapper
|
138
|
+
def map_input(input)
|
139
|
+
return input[:number]
|
140
|
+
end
|
141
|
+
|
142
|
+
def map_output(output)
|
143
|
+
return {:number => output, :completed => Time.now}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class MyDaemon < Oni::Daemon
|
148
|
+
set :mapper, MyMapper
|
149
|
+
set :worker, MyWorker
|
150
|
+
|
151
|
+
# Here you'd receive your message, e.g. from a queue. We'll use static
|
152
|
+
# data as an example.
|
153
|
+
def receive
|
154
|
+
yield({:number => 10})
|
155
|
+
end
|
156
|
+
|
157
|
+
# This would get executed upon completion of a job.
|
158
|
+
def complete(message, result, timings)
|
159
|
+
puts result
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
## License
|
164
|
+
|
165
|
+
The source code of this repository and Oni itself are licensed under the MIT
|
166
|
+
license unless specified otherwise. A copy of this license can be found in the
|
167
|
+
file "LICENSE" in the root directory of this repository.
|
168
|
+
|
169
|
+
[olery]: http://www.olery.com/
|
170
|
+
[daemon-kit]: https://github.com/kennethkalmer/daemon-kit
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'lib/oni/version'
|
2
|
+
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
require 'ci/reporter/rake/rspec'
|
6
|
+
|
7
|
+
CLEAN.include('coverage', 'yardoc')
|
8
|
+
|
9
|
+
Dir['./task/*.rake'].each do |task|
|
10
|
+
import(task)
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :test
|
data/doc/changelog.md
ADDED
data/doc/css/common.css
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
body
|
3
|
+
{
|
4
|
+
font-size: 14px;
|
5
|
+
line-height: 1.6;
|
6
|
+
margin: 0 auto;
|
7
|
+
max-width: 960px;
|
8
|
+
}
|
9
|
+
|
10
|
+
p code
|
11
|
+
{
|
12
|
+
background: #f2f2f2;
|
13
|
+
padding-left: 3px;
|
14
|
+
padding-right: 3px;
|
15
|
+
}
|
16
|
+
|
17
|
+
pre.code
|
18
|
+
{
|
19
|
+
font-size: 13px;
|
20
|
+
line-height: 1.4;
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* YARD uses generic table styles, using a special class means those tables
|
25
|
+
* don't get messed up.
|
26
|
+
*/
|
27
|
+
.table
|
28
|
+
{
|
29
|
+
border: 1px solid #ccc;
|
30
|
+
border-right: none;
|
31
|
+
border-collapse: separate;
|
32
|
+
border-spacing: 0;
|
33
|
+
text-align: left;
|
34
|
+
}
|
35
|
+
|
36
|
+
.table.full
|
37
|
+
{
|
38
|
+
width: 100%;
|
39
|
+
}
|
40
|
+
|
41
|
+
.table .field_name
|
42
|
+
{
|
43
|
+
min-width: 160px;
|
44
|
+
}
|
45
|
+
|
46
|
+
.table thead tr th.no_sort:first-child
|
47
|
+
{
|
48
|
+
width: 25px;
|
49
|
+
}
|
50
|
+
|
51
|
+
.table thead tr th, .table tbody tr td
|
52
|
+
{
|
53
|
+
border-bottom: 1px solid #ccc;
|
54
|
+
border-right: 1px solid #ccc;
|
55
|
+
min-width: 20px;
|
56
|
+
padding: 8px 5px;
|
57
|
+
text-align: left;
|
58
|
+
vertical-align: top;
|
59
|
+
}
|
60
|
+
|
61
|
+
.table tbody tr:last-child td
|
62
|
+
{
|
63
|
+
border-bottom: none;
|
64
|
+
}
|
65
|
+
|
66
|
+
.table tr:nth-child(odd) td
|
67
|
+
{
|
68
|
+
background: #f9f9f9;
|
69
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative '../lib/oni'
|
2
|
+
require 'net/https'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module GithubStatus
|
6
|
+
class Mapper < Oni::Mapper
|
7
|
+
def map_input(input)
|
8
|
+
return input['url']
|
9
|
+
end
|
10
|
+
|
11
|
+
def map_output(output)
|
12
|
+
return output['status']
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Worker < Oni::Worker
|
17
|
+
attr_reader :url
|
18
|
+
|
19
|
+
def initialize(url)
|
20
|
+
@url = url
|
21
|
+
end
|
22
|
+
|
23
|
+
def process
|
24
|
+
uri_object = URI.parse(url)
|
25
|
+
http = Net::HTTP.new(uri_object.host, uri_object.port)
|
26
|
+
http.use_ssl = true
|
27
|
+
request = Net::HTTP::Get.new(uri_object.request_uri)
|
28
|
+
response = http.request(request)
|
29
|
+
|
30
|
+
return JSON(response.body)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Daemon < Oni::Daemon
|
35
|
+
# Check GitHub every 10 minutes.
|
36
|
+
set :interval, 600
|
37
|
+
|
38
|
+
# Since this daemon does the same thing over and over we'll only run 1
|
39
|
+
# thread instead of multiple ones.
|
40
|
+
set :threads, 1
|
41
|
+
|
42
|
+
# The URL to check.
|
43
|
+
set :status_url, 'https://status.github.com/api/status.json'
|
44
|
+
|
45
|
+
set :mapper, Mapper
|
46
|
+
set :worker, Worker
|
47
|
+
|
48
|
+
def receive
|
49
|
+
loop do
|
50
|
+
# This is to mimic some kind of job coming from a queue.
|
51
|
+
yield({'url' => option(:status_url)})
|
52
|
+
|
53
|
+
sleep(option(:interval))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def complete(message, output, timings)
|
58
|
+
sec = timings.real.round(3)
|
59
|
+
|
60
|
+
puts "GitHub status: #{output}, retrieved in #{sec} seconds"
|
61
|
+
end
|
62
|
+
end # Daemon
|
63
|
+
end # GithubStatus
|
64
|
+
|
65
|
+
daemon = GithubStatus::Daemon.new
|
66
|
+
|
67
|
+
%w{INT TERM}.each do |signal|
|
68
|
+
trap(signal) do
|
69
|
+
puts 'Shutting down...'
|
70
|
+
|
71
|
+
daemon.stop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
daemon.start
|
data/jenkins.sh
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# This configuration file is used to test/build the project on Olery's private
|
2
|
+
# Jenkins instance. Patches containing changes to this file made by people
|
3
|
+
# outside of Olery will most likely be rejected.
|
4
|
+
|
5
|
+
# The name of the project, used for other settings such as the MySQL database
|
6
|
+
# and the package name.
|
7
|
+
PROJECT_NAME="oni"
|
8
|
+
|
9
|
+
# Whether or not to use a MySQL database, set to a non empty value to enable
|
10
|
+
# this. Enabling this will tell Jenkins to create/drop the database and to
|
11
|
+
# import any migrations if needed.
|
12
|
+
unset USE_MYSQL
|
13
|
+
|
14
|
+
# The command to run for the test suite. Junction itself doesn't have a test
|
15
|
+
# suite so we'll use a noop.
|
16
|
+
TEST_COMMAND="rake jenkins --trace"
|