isono 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +202 -0
- data/NOTICE +2 -0
- data/bin/cli +122 -0
- data/isono.gemspec +47 -0
- data/lib/ext/shellwords.rb +172 -0
- data/lib/isono.rb +61 -0
- data/lib/isono/amqp_client.rb +169 -0
- data/lib/isono/daemonize.rb +96 -0
- data/lib/isono/event_delegate_context.rb +56 -0
- data/lib/isono/event_observable.rb +86 -0
- data/lib/isono/logger.rb +48 -0
- data/lib/isono/manifest.rb +161 -0
- data/lib/isono/messaging_client.rb +116 -0
- data/lib/isono/models/event_log.rb +28 -0
- data/lib/isono/models/job_state.rb +35 -0
- data/lib/isono/models/node_state.rb +70 -0
- data/lib/isono/models/resource_instance.rb +35 -0
- data/lib/isono/node.rb +158 -0
- data/lib/isono/node_modules/base.rb +65 -0
- data/lib/isono/node_modules/data_store.rb +57 -0
- data/lib/isono/node_modules/event_channel.rb +72 -0
- data/lib/isono/node_modules/event_logger.rb +39 -0
- data/lib/isono/node_modules/job_channel.rb +86 -0
- data/lib/isono/node_modules/job_collector.rb +47 -0
- data/lib/isono/node_modules/job_worker.rb +152 -0
- data/lib/isono/node_modules/node_collector.rb +87 -0
- data/lib/isono/node_modules/node_heartbeat.rb +26 -0
- data/lib/isono/node_modules/rpc_channel.rb +482 -0
- data/lib/isono/rack.rb +67 -0
- data/lib/isono/rack/builder.rb +40 -0
- data/lib/isono/rack/data_store.rb +20 -0
- data/lib/isono/rack/job.rb +74 -0
- data/lib/isono/rack/map.rb +56 -0
- data/lib/isono/rack/object_method.rb +20 -0
- data/lib/isono/rack/proc.rb +50 -0
- data/lib/isono/rack/thread_pass.rb +22 -0
- data/lib/isono/resource_manifest.rb +273 -0
- data/lib/isono/runner/agent.rb +89 -0
- data/lib/isono/runner/rpc_server.rb +198 -0
- data/lib/isono/serializer.rb +43 -0
- data/lib/isono/thread_pool.rb +169 -0
- data/lib/isono/util.rb +212 -0
- metadata +185 -0
data/lib/isono.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Isono
|
4
|
+
VERSION='0.1.0'
|
5
|
+
|
6
|
+
autoload :Node, 'isono/node'
|
7
|
+
autoload :AmqpClient, 'isono/amqp_client'
|
8
|
+
autoload :Daemonize, 'isono/daemonize'
|
9
|
+
autoload :Util, 'isono/util'
|
10
|
+
autoload :ThreadPool, 'isono/thread_pool'
|
11
|
+
autoload :Logger, 'isono/logger'
|
12
|
+
autoload :Manifest, 'isono/manifest'
|
13
|
+
autoload :Serializer, 'isono/serializer'
|
14
|
+
autoload :EventObservable, 'isono/event_observable'
|
15
|
+
autoload :EventDelegateContext, 'isono/event_delegate_context'
|
16
|
+
autoload :ResourceManifest, 'isono/resource_manifest'
|
17
|
+
autoload :MessagingClient, 'isono/messaging_client'
|
18
|
+
module NodeModules
|
19
|
+
autoload :Base, 'isono/node_modules/base'
|
20
|
+
autoload :DataStore, 'isono/node_modules/data_store'
|
21
|
+
autoload :EventChannel, 'isono/node_modules/event_channel'
|
22
|
+
autoload :RpcChannel, 'isono/node_modules/rpc_channel'
|
23
|
+
autoload :NodeHeartbeat, 'isono/node_modules/node_heartbeat'
|
24
|
+
autoload :NodeCollector, 'isono/node_modules/node_collector'
|
25
|
+
autoload :EventLogger, 'isono/node_modules/event_logger'
|
26
|
+
autoload :JobWorker, 'isono/node_modules/job_worker'
|
27
|
+
autoload :JobChannel, 'isono/node_modules/job_channel'
|
28
|
+
autoload :JobCollector, 'isono/node_modules/job_collector'
|
29
|
+
end
|
30
|
+
module Runner
|
31
|
+
autoload :Agent, 'isono/runner/agent'
|
32
|
+
autoload :RpcServer, 'isono/runner/rpc_server'
|
33
|
+
end
|
34
|
+
module Rack
|
35
|
+
require 'isono/rack'
|
36
|
+
autoload :Builder, 'isono/rack/builder'
|
37
|
+
autoload :ObjectMethod, 'isono/rack/object_method'
|
38
|
+
autoload :Proc, 'isono/rack/proc'
|
39
|
+
autoload :ThreadPass, 'isono/rack/thread_pass'
|
40
|
+
autoload :Job, 'isono/rack/job'
|
41
|
+
autoload :DataStore, 'isono/rack/data_store'
|
42
|
+
autoload :Map, 'isono/rack/map'
|
43
|
+
end
|
44
|
+
|
45
|
+
module Models
|
46
|
+
autoload :NodeState, 'isono/models/node_state'
|
47
|
+
autoload :ResourceInstance, 'isono/models/resource_instance'
|
48
|
+
autoload :EventLog, 'isono/models/event_log'
|
49
|
+
autoload :JobState, 'isono/models/job_state'
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
def home
|
54
|
+
if Kernel.const_defined?(:Gem) && (gemspec = Gem.loaded_specs['isono'])
|
55
|
+
gemspec.full_gem_path
|
56
|
+
else
|
57
|
+
File.expand_path('../../', __FILE__)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require 'eventmachine'
|
6
|
+
require 'amqp'
|
7
|
+
require 'mq'
|
8
|
+
|
9
|
+
require 'uri/generic'
|
10
|
+
|
11
|
+
module URI
|
12
|
+
# Declare amqp:// URI scheme.
|
13
|
+
class AMQP < Generic
|
14
|
+
COMPONENT = [
|
15
|
+
:scheme,
|
16
|
+
:userinfo, :host, :port,
|
17
|
+
:path
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
DEFAULT_PORT=5672
|
21
|
+
|
22
|
+
def self.build(args)
|
23
|
+
tmp = Util::make_components_hash(self, args)
|
24
|
+
return super(tmp)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(*args)
|
28
|
+
args[5] = '/' if args[5].nil? || args[5] == ''
|
29
|
+
super(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
alias :vhost :path
|
33
|
+
alias :vhost= :path=
|
34
|
+
end
|
35
|
+
|
36
|
+
@@schemes['AMQP'] = AMQP
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
module Isono
|
41
|
+
# AMQP Client module for master and agent
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# class Client
|
45
|
+
# include Isono::AmqpClient
|
46
|
+
#
|
47
|
+
# def logger()
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
module AmqpClient
|
51
|
+
attr_reader :mq, :amqp_client
|
52
|
+
|
53
|
+
def amqp_server_uri
|
54
|
+
raise "The connection is not established yet." unless @amqp_client && connected?
|
55
|
+
|
56
|
+
URI::AMQP.build(:host => @amqp_client.settings[:host],
|
57
|
+
:port => @amqp_client.settings[:port],
|
58
|
+
:path => @amqp_client.settings[:vhost]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def connect(broker_uri, *args, &blk)
|
63
|
+
raise "the connection is still alive for: #{amqp_server_uri}" if connected?
|
64
|
+
|
65
|
+
broker_uri = URI.parse(broker_uri.to_s) unless broker_uri.is_a?(URI)
|
66
|
+
default = ::AMQP.settings
|
67
|
+
opts = {:host => broker_uri.host,
|
68
|
+
:port => broker_uri.port || default[:port],
|
69
|
+
:vhost => broker_uri.vhost || default[:vhost],
|
70
|
+
:user=>broker_uri.user || default[:user],
|
71
|
+
:pass=>broker_uri.password ||default[:pass]
|
72
|
+
}
|
73
|
+
opts.merge!(args) if args.is_a?(Hash)
|
74
|
+
|
75
|
+
@amqp_client = ::AMQP.connect(opts)
|
76
|
+
@amqp_client.instance_eval {
|
77
|
+
def settings
|
78
|
+
@settings
|
79
|
+
end
|
80
|
+
}
|
81
|
+
@amqp_client.callback {
|
82
|
+
on_connect
|
83
|
+
if blk
|
84
|
+
blk.arity == 1 ? blk.call(:success) : blk.call
|
85
|
+
end
|
86
|
+
}
|
87
|
+
@amqp_client.errback {
|
88
|
+
logger.error("Failed to connect to the broker: #{amqp_server_uri}")
|
89
|
+
blk.call(:error) if blk && blk.arity == 1
|
90
|
+
}
|
91
|
+
# Note: Thread.current[:mq] is utilized in amqp gem.
|
92
|
+
Thread.current[:mq] = ::MQ.new(@amqp_client)
|
93
|
+
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def connected?
|
98
|
+
!@amqp_client.nil? && @amqp_client.connected?
|
99
|
+
end
|
100
|
+
|
101
|
+
def amq
|
102
|
+
raise 'AMQP connection is not established yet' unless connected?
|
103
|
+
Thread.current[:mq]
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_connect
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_close
|
110
|
+
end
|
111
|
+
|
112
|
+
def close(&blk)
|
113
|
+
return unless connected?
|
114
|
+
|
115
|
+
@amqp_client.close {
|
116
|
+
begin
|
117
|
+
on_close
|
118
|
+
blk.call if blk
|
119
|
+
ensure
|
120
|
+
@amqp_client = nil
|
121
|
+
Thread.current[:mq] = nil
|
122
|
+
end
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
# Publish a message to the designated exchange.
|
127
|
+
#
|
128
|
+
# @param [String] exname The exchange name
|
129
|
+
# @param [String] message Message body to be sent
|
130
|
+
# @param [Hash] opts Options with the message.
|
131
|
+
# :key => 'keyname'
|
132
|
+
# @return [void]
|
133
|
+
#
|
134
|
+
# @example Want to broadcast the data to all bound queues:
|
135
|
+
# publish_to('topic exchange', 'data', :key=>'*')
|
136
|
+
# @example Want to send the data to the specific queue(s):
|
137
|
+
# publish_to('exchange name', 'group.1', 'data')
|
138
|
+
def publish_to(exname, message, opts={})
|
139
|
+
EventMachine.schedule {
|
140
|
+
ex = amq.exchanges[exname] || raise("Undefined exchange name : #{exname}")
|
141
|
+
case ex.type
|
142
|
+
when :topic
|
143
|
+
unless opts.has_key? :key
|
144
|
+
opts[:key] = '*'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
ex.publish(Serializer.instance.marshal(message), opts)
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def define_queue(queue_name, exchange_name, opts={}, &blk)
|
152
|
+
q = amq.queue(queue_name, opts)
|
153
|
+
amq.exchanges.has_key? exchange_name
|
154
|
+
q.bind( exchange_name, opts ).subscribe &blk
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def identity_queue(unique_id)
|
159
|
+
amq.direct('identity')
|
160
|
+
begin
|
161
|
+
define_queue("ident.#{unique_id}", "identity", {:exclusive=>true, :nowait=>false})
|
162
|
+
rescue MQ::Error => e
|
163
|
+
logger.error("The node having same ID already exists: #{unique_id}")
|
164
|
+
raise e
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'daemons/daemonize'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module Process
|
7
|
+
# Returns +true+ the process identied by +pid+ is running.
|
8
|
+
def running?(pid)
|
9
|
+
Process.getpgid(pid) != -1
|
10
|
+
rescue Errno::ESRCH
|
11
|
+
false
|
12
|
+
end
|
13
|
+
module_function :running?
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
module Wakame
|
18
|
+
module Daemonize
|
19
|
+
# Change privileges of the process
|
20
|
+
# to the specified user and group.
|
21
|
+
def change_privilege(user, group=user)
|
22
|
+
Wakame.log.info("Changing process privilege to #{user}:#{group}")
|
23
|
+
|
24
|
+
uid, gid = Process.euid, Process.egid
|
25
|
+
target_uid = Etc.getpwnam(user).uid
|
26
|
+
target_gid = Etc.getgrnam(group).gid
|
27
|
+
|
28
|
+
if uid != target_uid || gid != target_gid
|
29
|
+
if pid_file && File.exist?(pid_file) && uid == 0
|
30
|
+
File.chown(target_uid, target_gid, pid_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Change process ownership
|
34
|
+
Process.initgroups(user, target_gid)
|
35
|
+
Process::GID.change_privilege(target_gid)
|
36
|
+
Process::UID.change_privilege(target_uid)
|
37
|
+
end
|
38
|
+
rescue Errno::EPERM => e
|
39
|
+
Wakame.log.error("Couldn't change user and group to #{user}:#{group}: #{e}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def pid_file
|
43
|
+
@options[:pid_file]
|
44
|
+
end
|
45
|
+
|
46
|
+
def pid
|
47
|
+
File.exist?(pid_file) ? open(pid_file).read.to_i : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def setup_pidfile
|
51
|
+
#raise 'Please implement pid_file() method' unless respond_to? :pid_file
|
52
|
+
|
53
|
+
unless File.exist?(File.dirname(pid_file))
|
54
|
+
FileUtils.mkpath(File.dirname(pid_file))
|
55
|
+
end
|
56
|
+
|
57
|
+
open(pid_file, "w") { |f| f.write(Process.pid) }
|
58
|
+
File.chmod(0644, pid_file)
|
59
|
+
end
|
60
|
+
|
61
|
+
def daemonize(log_path)
|
62
|
+
# Cleanup stale pidfile or prevent from multiple process running.
|
63
|
+
if File.exist?(pid_file)
|
64
|
+
if pid && Process.running?(pid)
|
65
|
+
raise "#{pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
|
66
|
+
"Stop the process or delete #{pid_file}."
|
67
|
+
else
|
68
|
+
Wakame.log.info "Deleting the stale PID file: #{pid_file}"
|
69
|
+
remove_pidfile
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
::Daemonize.daemonize(log_path, File.basename($0.to_s))
|
74
|
+
|
75
|
+
setup_pidfile
|
76
|
+
#Signal.trap('HUP') {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_restart(&blk)
|
80
|
+
@on_restart = blk
|
81
|
+
end
|
82
|
+
|
83
|
+
def restart
|
84
|
+
raise '' if @on_restart.nil?
|
85
|
+
|
86
|
+
@on_restart.call
|
87
|
+
end
|
88
|
+
|
89
|
+
def remove_pidfile
|
90
|
+
File.delete(pid_file) if pid_file && File.exist?(pid_file)
|
91
|
+
rescue => e
|
92
|
+
Wakame.log.error(e)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'statemachine'
|
4
|
+
|
5
|
+
module Isono
|
6
|
+
# Catch all the event from the Statemachine and delegate.
|
7
|
+
class EventDelegateContext
|
8
|
+
include EventObservable
|
9
|
+
include Logger
|
10
|
+
|
11
|
+
def initialize(stm)
|
12
|
+
raise ArgumentError unless stm.is_a? Statemachine::Statemachine
|
13
|
+
initialize_event_observable
|
14
|
+
@stm = stm
|
15
|
+
@stm.context = self
|
16
|
+
inject_event_handlers
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
def on_event_fired(evtype, *args)
|
21
|
+
logger.debug("#{evtype}")
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
# analyze the current statemachine object and inject
|
26
|
+
# event handlers where the stm can trigger the events:
|
27
|
+
# - all entry/exit events from/to each state.
|
28
|
+
# - transition events
|
29
|
+
def inject_event_handlers
|
30
|
+
states = @stm.instance_variable_get(:@states)
|
31
|
+
states.each { |k, st|
|
32
|
+
prev = st.entry_action
|
33
|
+
st.entry_action = proc { |*args|
|
34
|
+
on_entry_state(st.id, *args)
|
35
|
+
}
|
36
|
+
prev = st.exit_action
|
37
|
+
st.exit_action = proc { |*args|
|
38
|
+
on_exit_state(st.id, *args)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_entry_state(state, *args)
|
44
|
+
evname = "on_entry_of_#{state}"
|
45
|
+
fire_event(:on_entry_state, {:evname=>evname.to_sym, :args=>args})
|
46
|
+
fire_event(evname.to_sym, args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_exit_state(state, *args)
|
50
|
+
evname = "on_exit_of_#{state}"
|
51
|
+
fire_event(:on_exit_state, {:evname=>evname.to_sym, :args=>args})
|
52
|
+
fire_event(evname.to_sym, args)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Isono
|
4
|
+
# Add event handling support to the arbitrary class.
|
5
|
+
# TODO: make it thread safe.
|
6
|
+
module EventObservable
|
7
|
+
class Timeout
|
8
|
+
end
|
9
|
+
|
10
|
+
def fire_event(evtype, *args)
|
11
|
+
if logger && @debug_event
|
12
|
+
logger.debug("fire_event(#{evtype}, #{args.nil? ? 'nil' : args.inspect})")
|
13
|
+
end
|
14
|
+
begin
|
15
|
+
on_event_fired(evtype, *args)
|
16
|
+
rescue => e
|
17
|
+
if logger
|
18
|
+
logger.error(e)
|
19
|
+
else
|
20
|
+
puts e
|
21
|
+
end
|
22
|
+
end
|
23
|
+
return unless @tickets[evtype]
|
24
|
+
|
25
|
+
deleted_tickets = []
|
26
|
+
@tickets[evtype].each { |ticket|
|
27
|
+
h = @handlers[ticket]
|
28
|
+
if h
|
29
|
+
begin
|
30
|
+
h.call(*args)
|
31
|
+
rescue Exception => e
|
32
|
+
logger.error("caught exception: #{ticket}, proc=#{h.to_s}: #{e.to_s}")
|
33
|
+
logger.error(e)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
deleted_tickets << ticket
|
37
|
+
end
|
38
|
+
}
|
39
|
+
|
40
|
+
@tickets[evtype] -= deleted_tickets
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_observer(evtype, &blk)
|
44
|
+
ticket = Util.gen_id
|
45
|
+
@handlers[ticket] = blk
|
46
|
+
(@tickets[evtype] ||= []) << ticket
|
47
|
+
ticket
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_observer_once(evtype, timeout=0, &blk)
|
51
|
+
ticket = add_observer(evtype) { |*args|
|
52
|
+
begin
|
53
|
+
blk.call(*args)
|
54
|
+
ensure
|
55
|
+
remove_observer(ticket)
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
if timeout > 0
|
60
|
+
EM.add_timer(timeout) {
|
61
|
+
if @handlers.has_key? ticket
|
62
|
+
blk.call(Timeout.new)
|
63
|
+
remove_observer(ticket)
|
64
|
+
end
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
ticket
|
69
|
+
end
|
70
|
+
|
71
|
+
def remove_observer(ticket)
|
72
|
+
@handlers.delete(ticket)
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
def initialize_event_observable
|
77
|
+
@tickets = {}
|
78
|
+
@handlers = {}
|
79
|
+
@debug_event = false
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def on_event_fired(evtype, *args)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|