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