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/logger.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'log4r'
|
4
|
+
|
5
|
+
module Isono
|
6
|
+
# Injects +logger+ method to the included class.
|
7
|
+
# The output message from the logger methods starts the module name trailing message body.
|
8
|
+
module Logger
|
9
|
+
|
10
|
+
# Isono top level logger
|
11
|
+
rootlog = Log4r::Logger.new('Isono')
|
12
|
+
formatter = Log4r::PatternFormatter.new(:depth => 9999, # stack trace depth
|
13
|
+
:pattern => "%d %c [%l]: %M",
|
14
|
+
:date_format => "%Y/%m/%d %H:%M:%S"
|
15
|
+
)
|
16
|
+
rootlog.add(Log4r::StdoutOutputter.new('stdout', :formatter => formatter))
|
17
|
+
|
18
|
+
|
19
|
+
def self.included(klass)
|
20
|
+
klass.class_eval {
|
21
|
+
|
22
|
+
@class_logger = Log4r::Logger.new(klass.to_s)
|
23
|
+
|
24
|
+
def self.logger
|
25
|
+
@class_logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def logger
|
29
|
+
@instance_logger || self.class.logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.logger_name
|
33
|
+
@class_logger.path
|
34
|
+
end
|
35
|
+
|
36
|
+
#def self.logger_name=(name)
|
37
|
+
# @logger_name = name
|
38
|
+
#end
|
39
|
+
|
40
|
+
def set_instance_logger(suffix=nil)
|
41
|
+
suffix ||= self.__id__.abs.to_s(16)
|
42
|
+
@instance_logger = Log4r::Logger.new("#{self.class} for #{suffix}")
|
43
|
+
end
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Isono
|
7
|
+
# Deals with .isono manifest file and its data.
|
8
|
+
class Manifest
|
9
|
+
# Loads .isono manifest file
|
10
|
+
# @param [String] path to which you want to load.
|
11
|
+
# @return [Isono::Manifest] new manifest object
|
12
|
+
def self.load_file(path)
|
13
|
+
buf = File.read(path)
|
14
|
+
# use the parent directory as the application root.
|
15
|
+
app_root = File.dirname(path)
|
16
|
+
m = eval("#{self.to_s}.new('#{app_root}') {\n" + buf + "\n}", TOPLEVEL_BINDING, path)
|
17
|
+
|
18
|
+
# use the manifest filename as the node name if not set.
|
19
|
+
m.node_name(File.basename(path, '.isono')) if m.node_name.nil?
|
20
|
+
|
21
|
+
m
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :node_modules, :app_root
|
25
|
+
|
26
|
+
# @param [String] app_root Application root folder
|
27
|
+
# @param [block]
|
28
|
+
def initialize(app_root, &blk)
|
29
|
+
@node_modules = []
|
30
|
+
resolve_abs_app_root(app_root)
|
31
|
+
@config = ConfigStruct.new
|
32
|
+
@config.app_root = app_root
|
33
|
+
|
34
|
+
instance_eval(&blk) if blk
|
35
|
+
load_config
|
36
|
+
end
|
37
|
+
|
38
|
+
# Regist a node module class to be initialized/terminated.
|
39
|
+
# @param [Class] mod_class
|
40
|
+
def load_module(mod_class, *args)
|
41
|
+
unless mod_class.is_a?(Class) && mod_class < Isono::NodeModules::Base
|
42
|
+
raise ArgumentError, ""
|
43
|
+
end
|
44
|
+
|
45
|
+
return if @node_modules.find{|i| i[0] == mod_class }
|
46
|
+
|
47
|
+
sec_builder = mod_class.instance_variable_get(:@config_section_builder)
|
48
|
+
if sec_builder.is_a? Proc
|
49
|
+
sec_name = mod_class.instance_variable_get(:@config_section_name)
|
50
|
+
#sec_builder.call(ConfigStructBuilder.new(@config.add_section(sec_name)))
|
51
|
+
ConfigStructBuilder.new(@config.add_section(sec_name)).instance_eval &sec_builder
|
52
|
+
end
|
53
|
+
@node_modules << [mod_class, *args]
|
54
|
+
end
|
55
|
+
alias manager load_module
|
56
|
+
|
57
|
+
def node_name(name=nil)
|
58
|
+
@node_name = name.to_s if name
|
59
|
+
@node_name
|
60
|
+
end
|
61
|
+
|
62
|
+
def node_instance_id(instance_id=nil)
|
63
|
+
@node_instance_id = instance_id.to_s if instance_id
|
64
|
+
@node_instance_id
|
65
|
+
end
|
66
|
+
|
67
|
+
def node_id
|
68
|
+
"#{@node_name}-#{@node_instance_id}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def config_path(path=nil)
|
72
|
+
@config_path = path if path
|
73
|
+
@config_path
|
74
|
+
end
|
75
|
+
|
76
|
+
def config(&blk)
|
77
|
+
if blk
|
78
|
+
@config.instance_eval &blk
|
79
|
+
end
|
80
|
+
@config
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
private
|
85
|
+
# load config file and merge up with the config tree.
|
86
|
+
# it will not work when the config_path is nil or the file is missed
|
87
|
+
def load_config
|
88
|
+
if @config_path && File.exist?(@config_path)
|
89
|
+
buf = File.read(@config_path)
|
90
|
+
eval("#{buf}", binding, @config_path)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def resolve_abs_app_root(app_root_path)
|
95
|
+
pt = Pathname.new(app_root_path)
|
96
|
+
if pt.absolute?
|
97
|
+
@app_root = app_root_path
|
98
|
+
else
|
99
|
+
@app_root = pt.realpath
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
class ConfigStruct < OpenStruct
|
105
|
+
attr_reader :desc
|
106
|
+
|
107
|
+
def initialize()
|
108
|
+
super
|
109
|
+
@desc = {}
|
110
|
+
end
|
111
|
+
|
112
|
+
# create sub config tree
|
113
|
+
def add_section(name)
|
114
|
+
newsec = self.class.new
|
115
|
+
self.instance_eval %Q"
|
116
|
+
def #{name}(&blk)
|
117
|
+
blk.call(self) if blk
|
118
|
+
@table[:#{name}]
|
119
|
+
end
|
120
|
+
"
|
121
|
+
@table[name.to_sym] = newsec
|
122
|
+
end
|
123
|
+
|
124
|
+
def inspect
|
125
|
+
@table.keys.map { |k|
|
126
|
+
"#{k}=#{@table[k]}"
|
127
|
+
}.join(', ')
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
class ConfigStructBuilder
|
134
|
+
def initialize(config)
|
135
|
+
@cur_desc=nil
|
136
|
+
@config = config
|
137
|
+
end
|
138
|
+
|
139
|
+
def add_config(name, default_val=nil)
|
140
|
+
@config.send("#{name}=".to_sym, default_val)
|
141
|
+
@config.desc[name.to_sym] = @cur_desc
|
142
|
+
|
143
|
+
@cur_desc = nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def desc(desc)
|
147
|
+
@cur_desc = desc
|
148
|
+
end
|
149
|
+
|
150
|
+
def method_missing(name, *args)
|
151
|
+
return if name.to_sym == :add_config
|
152
|
+
if name.to_s =~ /=$/
|
153
|
+
add_config(name.to_s.sub(/=$/,''), args[0])
|
154
|
+
else
|
155
|
+
add_config(name, *args)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'isono'
|
4
|
+
require 'eventmachine'
|
5
|
+
|
6
|
+
module Isono
|
7
|
+
# @example Sync RPC call with object method.
|
8
|
+
# mc = MessagingClient.start
|
9
|
+
# puts mc.request('endpoint', 'func1', xxxx, xxxx)
|
10
|
+
# puts mc.request('endpoint', 'func2', xxx, xxx)
|
11
|
+
#
|
12
|
+
# @example Sync RPC call using delegated object
|
13
|
+
# mc = MessagingClient.start
|
14
|
+
# endpoint = mc.sync_rpc('endpoint')
|
15
|
+
# endpoint.func1(xxxx, xxxx)
|
16
|
+
# endpoint.func2(xxx, xxx)
|
17
|
+
#
|
18
|
+
class MessagingClient < Node
|
19
|
+
include Logger
|
20
|
+
|
21
|
+
def self.start(amqp_uri, manifest=nil, &blk)
|
22
|
+
node = self.new(manifest, &blk)
|
23
|
+
|
24
|
+
if EventMachine.reactor_thread?
|
25
|
+
EventMachine.schedule {
|
26
|
+
node.connect(amqp_uri)
|
27
|
+
}
|
28
|
+
else
|
29
|
+
q = ::Queue.new
|
30
|
+
EventMachine.schedule {
|
31
|
+
node.connect(amqp_uri) { |type|
|
32
|
+
q << type
|
33
|
+
}
|
34
|
+
}
|
35
|
+
case q.deq
|
36
|
+
when :success
|
37
|
+
when :error
|
38
|
+
raise "Connection failed: #{amqp_uri}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
node
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop
|
46
|
+
if connected?
|
47
|
+
close {
|
48
|
+
EventMachine.schedule {
|
49
|
+
EventMachine.stop
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(m=nil, &blk)
|
56
|
+
m ||= Manifest.new(Dir.pwd) {
|
57
|
+
node_name 'msgclient'
|
58
|
+
node_instance_id Util.gen_id
|
59
|
+
|
60
|
+
load_module NodeModules::EventChannel
|
61
|
+
load_module NodeModules::RpcChannel
|
62
|
+
load_module NodeModules::JobChannel
|
63
|
+
}
|
64
|
+
m.instance_eval(&blk) if blk
|
65
|
+
super(m)
|
66
|
+
end
|
67
|
+
|
68
|
+
class RpcSyncDelegator
|
69
|
+
attr_reader :endpoint
|
70
|
+
|
71
|
+
def initialize(rpc, endpoint, opts={})
|
72
|
+
@rpc = rpc
|
73
|
+
@endpoint = endpoint
|
74
|
+
@opts = {:timeout=>0.0, :oneshot=>false}.merge(opts)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def method_missing(m, *args)
|
79
|
+
if @opts[:oneshot]
|
80
|
+
oneshot_request(m, *args)
|
81
|
+
else
|
82
|
+
normal_request(m, *args)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def oneshot_request(m, *args)
|
87
|
+
@rpc.request(@endpoint, m, *args) { |req|
|
88
|
+
req.oneshot = true
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def normal_request(m, *args)
|
93
|
+
@rpc.request(@endpoint, m, *args)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def sync_rpc(endpoint, opts={})
|
98
|
+
rpc = NodeModules::RpcChannel.new(self)
|
99
|
+
RpcSyncDelegator.new(rpc, endpoint, opts)
|
100
|
+
end
|
101
|
+
|
102
|
+
def request(endpoint, key, *args, &blk)
|
103
|
+
rpc = NodeModules::RpcChannel.new(self)
|
104
|
+
rpc.request(endpoint, key, *args, &blk)
|
105
|
+
end
|
106
|
+
|
107
|
+
def submit(job_endpoint, key, *args)
|
108
|
+
NodeModules::JobChannel.new(self).submit(job_endpoint, key, *args)
|
109
|
+
end
|
110
|
+
|
111
|
+
def event_publish(evname, opts={})
|
112
|
+
NodeModules::EventChannel.new(self).publish(evname, opts)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Isono
|
4
|
+
module Models
|
5
|
+
class EventLog < Sequel::Model
|
6
|
+
include Logger
|
7
|
+
plugin :schema
|
8
|
+
plugin :hook_class_methods
|
9
|
+
|
10
|
+
set_schema {
|
11
|
+
primary_key :id, :type => Integer, :auto_increment=>true, :unsigned=>true
|
12
|
+
column :event, :varchar, :size=>80, :null=>false
|
13
|
+
column :sender, :varchar, :size=>80, :null=>false
|
14
|
+
column :message, :text, :null=>false
|
15
|
+
column :publised_at, :datetime, :null=>false
|
16
|
+
column :created_at, :datetime, :null=>false
|
17
|
+
#index [:agent_id, :boot_token], {:unique=>true}
|
18
|
+
}
|
19
|
+
|
20
|
+
before_create(:set_created_at) do
|
21
|
+
self.created_at = Time.now
|
22
|
+
end
|
23
|
+
before_update(:set_updated_at) do
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'sequel/model'
|
4
|
+
|
5
|
+
module Isono
|
6
|
+
module Models
|
7
|
+
class JobState < Sequel::Model
|
8
|
+
include Logger
|
9
|
+
plugin :schema
|
10
|
+
plugin :hook_class_methods
|
11
|
+
|
12
|
+
set_schema {
|
13
|
+
primary_key :id, :type => Integer, :auto_increment=>true, :unsigned=>true
|
14
|
+
column :job_id, :varchar, :size=>80, :null=>false
|
15
|
+
column :parent_job_id, :varchar, :size=>80, :null=>true
|
16
|
+
column :node_id, :varchar, :size=>80, :null=>false
|
17
|
+
column :state, :varchar, :size=>10, :null=>false
|
18
|
+
column :message, :text, :null=>false, :default=>''
|
19
|
+
column :created_at, :datetime, :null=>false
|
20
|
+
column :updated_at, :datetime, :null=>false
|
21
|
+
column :started_at, :datetime, :null=>true
|
22
|
+
column :finished_at, :datetime, :null=>true
|
23
|
+
index :job_id, {:unique=>true}
|
24
|
+
}
|
25
|
+
|
26
|
+
before_create(:set_created_at) do
|
27
|
+
self.created_at = self.updated_at= Time.now
|
28
|
+
end
|
29
|
+
before_update(:set_updated_at) do
|
30
|
+
self.updated_at= Time.now
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'sequel/model'
|
4
|
+
require 'statemachine'
|
5
|
+
|
6
|
+
module Isono
|
7
|
+
module Models
|
8
|
+
class NodeState < Sequel::Model
|
9
|
+
include Logger
|
10
|
+
plugin :schema
|
11
|
+
plugin :hook_class_methods
|
12
|
+
|
13
|
+
set_schema {
|
14
|
+
primary_key :id, :type => Integer, :auto_increment=>true, :unsigned=>true
|
15
|
+
column :node_id, :varchar, :size=>80, :null=>false
|
16
|
+
column :boot_token, :varchar, :size=>10, :null=>false
|
17
|
+
column :state, :varchar, :size=>10
|
18
|
+
column :created_at, :datetime, :null=>false
|
19
|
+
column :updated_at, :datetime, :null=>false
|
20
|
+
column :last_ping_at, :datetime, :null=>false
|
21
|
+
index :node_id, {:unique=>true}
|
22
|
+
}
|
23
|
+
|
24
|
+
before_create(:set_created_at) do
|
25
|
+
self.updated_at = self.created_at = Time.now
|
26
|
+
end
|
27
|
+
before_update(:set_updated_at) do
|
28
|
+
self.updated_at = Time.now
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def state_machine
|
33
|
+
model = self
|
34
|
+
st = Statemachine.build do
|
35
|
+
startstate :init
|
36
|
+
trans :init, :on_ping, :online, proc {model.last_ping_at = Time.now}
|
37
|
+
trans :online, :on_timeout, :timeout
|
38
|
+
trans :timeout, :on_ping, :online, proc {model.last_ping_at = Time.now}
|
39
|
+
trans :online, :on_unmonitor, :offline
|
40
|
+
trans :timeout, :on_unmonitor, :offline
|
41
|
+
# do nothing on transition from and to the same state
|
42
|
+
trans :online, :on_ping, :online, proc {model.last_ping_at = Time.now}
|
43
|
+
trans :timeout, :on_timeout, :timeout
|
44
|
+
|
45
|
+
on_entry_of :online, proc {
|
46
|
+
model.state = :online
|
47
|
+
}
|
48
|
+
on_entry_of :timeout, proc {
|
49
|
+
model.state = :timeout
|
50
|
+
}
|
51
|
+
on_entry_of :offline, proc {
|
52
|
+
model.state = :offline
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
if self[:state]
|
57
|
+
if st.has_state(self[:state].to_sym)
|
58
|
+
st.state = self[:state].to_sym
|
59
|
+
else
|
60
|
+
raise "Unknown state: #{self[:state]}"
|
61
|
+
end
|
62
|
+
else
|
63
|
+
st.reset
|
64
|
+
end
|
65
|
+
st
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|