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.
Files changed (43) hide show
  1. data/LICENSE +202 -0
  2. data/NOTICE +2 -0
  3. data/bin/cli +122 -0
  4. data/isono.gemspec +47 -0
  5. data/lib/ext/shellwords.rb +172 -0
  6. data/lib/isono.rb +61 -0
  7. data/lib/isono/amqp_client.rb +169 -0
  8. data/lib/isono/daemonize.rb +96 -0
  9. data/lib/isono/event_delegate_context.rb +56 -0
  10. data/lib/isono/event_observable.rb +86 -0
  11. data/lib/isono/logger.rb +48 -0
  12. data/lib/isono/manifest.rb +161 -0
  13. data/lib/isono/messaging_client.rb +116 -0
  14. data/lib/isono/models/event_log.rb +28 -0
  15. data/lib/isono/models/job_state.rb +35 -0
  16. data/lib/isono/models/node_state.rb +70 -0
  17. data/lib/isono/models/resource_instance.rb +35 -0
  18. data/lib/isono/node.rb +158 -0
  19. data/lib/isono/node_modules/base.rb +65 -0
  20. data/lib/isono/node_modules/data_store.rb +57 -0
  21. data/lib/isono/node_modules/event_channel.rb +72 -0
  22. data/lib/isono/node_modules/event_logger.rb +39 -0
  23. data/lib/isono/node_modules/job_channel.rb +86 -0
  24. data/lib/isono/node_modules/job_collector.rb +47 -0
  25. data/lib/isono/node_modules/job_worker.rb +152 -0
  26. data/lib/isono/node_modules/node_collector.rb +87 -0
  27. data/lib/isono/node_modules/node_heartbeat.rb +26 -0
  28. data/lib/isono/node_modules/rpc_channel.rb +482 -0
  29. data/lib/isono/rack.rb +67 -0
  30. data/lib/isono/rack/builder.rb +40 -0
  31. data/lib/isono/rack/data_store.rb +20 -0
  32. data/lib/isono/rack/job.rb +74 -0
  33. data/lib/isono/rack/map.rb +56 -0
  34. data/lib/isono/rack/object_method.rb +20 -0
  35. data/lib/isono/rack/proc.rb +50 -0
  36. data/lib/isono/rack/thread_pass.rb +22 -0
  37. data/lib/isono/resource_manifest.rb +273 -0
  38. data/lib/isono/runner/agent.rb +89 -0
  39. data/lib/isono/runner/rpc_server.rb +198 -0
  40. data/lib/isono/serializer.rb +43 -0
  41. data/lib/isono/thread_pool.rb +169 -0
  42. data/lib/isono/util.rb +212 -0
  43. metadata +185 -0
@@ -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