isono 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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