oni 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|