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/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
|