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