isono 0.1.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.
- 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
|