bsl-thor 0.0.2

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/lib/ThorMaster.rb ADDED
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $KCODE="UTF8"
4
+
5
+ # Options parsing gems
6
+ require 'optparse'
7
+ require 'pp'
8
+
9
+ # YAML
10
+ require 'yaml'
11
+
12
+ # Rubygems
13
+ require 'rubygems'
14
+
15
+ # Amqp Gems
16
+ require 'amqp'
17
+ #require 'mq'
18
+
19
+ # Event machine
20
+ require 'eventmachine'
21
+
22
+ # DB Related stuff
23
+ require 'active_record'
24
+ require 'pg'
25
+
26
+ # Custom gems
27
+ require File.join(File.dirname(__FILE__), 'ThorApplication.rb')
28
+ require 'Bsl'
29
+
30
+ module Thor
31
+ class AppMaster < Thor::Application
32
+ attr_accessor :options, :sql_connection, :request_exit
33
+
34
+ def initialize(opts = {})
35
+ super(opts)
36
+
37
+ @@AMQP_DEFAULT_RETRY_INTERVAL = 3
38
+ @@AMQP_MAX_RETRY_INTERVAL = (30)
39
+ @@AMQP_MAX_RETRY_ATTEMPS = -1
40
+ @@AMQP_RETRY_MULTIPLER = 1.5
41
+
42
+ @amqp_retry_interval = @@AMQP_DEFAULT_RETRY_INTERVAL
43
+ @amqp_retry_attempt = 0
44
+
45
+ # Signalizes that application wants exit for some reason
46
+ @request_exit = false
47
+
48
+ # AMQP options
49
+ @options[:amqp_host] = "localhost"
50
+ @options[:amqp_port] = 1234
51
+ @options[:amqp_user] = "user"
52
+ @options[:amqp_password] = "password"
53
+ @options[:amqp_vhost] = "my-vhost"
54
+
55
+ @options[:sql_adapter] = "postgresql"
56
+ @options[:sql_host] = "host"
57
+ @options[:sql_port] = 5432
58
+ @options[:sql_user] = "user"
59
+ @options[:sql_password] = "password"
60
+ @options[:sql_database] = "database"
61
+
62
+ @sql_connection = nil
63
+
64
+ initialize_optparser { |opts|
65
+ ############################
66
+ # AMQP Section
67
+ ############################
68
+ # AMQP Host
69
+ opts.on( '-H', '--amqp-host STRING', "AMQP Server hostname") do |host|
70
+ @options[:amqp_host] = host
71
+ end
72
+
73
+ # AMQP Port
74
+ opts.on( '-p', '--amqp-port NUM', "AMQP Server port number") do |port|
75
+ @options[:amqp_port] = port
76
+ end
77
+
78
+ # AMQP Username
79
+ opts.on( '-u', '--amqp-user STRING', "AMQP Username") do |user|
80
+ @options[:amqp_user] = user
81
+ end
82
+
83
+ # AMQP Password
84
+ opts.on( '-P', '--amqp-password STRING', "AMQP Password") do |password|
85
+ @options[:amqp_password] = password
86
+ end
87
+
88
+ # AMQP Vhost
89
+ opts.on( '-V', '--amqp-vhost STRING', "AMQP Virtual Host") do |vhost|
90
+ @options[:amqp_vhost] = vhost
91
+ end
92
+
93
+ ############################
94
+ # Sql section
95
+ ############################
96
+ # SQL Adapter
97
+ opts.on( '-sA', '--sql-adapter STRING', "SQL Adapter") do |adapter|
98
+ @options[:sql_adapter] = adapter
99
+ end
100
+
101
+ # SQL Host
102
+ opts.on( '-sH', '--sql-host STRING', "SQL Server hostname") do |host|
103
+ @options[:sql_host] = host
104
+ end
105
+
106
+ # SQL Port
107
+ opts.on( '-sp', '--sql-port NUM', "SQL Server port number") do |port|
108
+ @options[:sql_port] = port
109
+ end
110
+
111
+ # SQL Username
112
+ opts.on( '-su', '--sql-user STRING', "SQL Username") do |user|
113
+ @options[:sql_user] = user
114
+ end
115
+
116
+ # SQL Password
117
+ opts.on( '-sP', '--sql-password STRING', "SQL Password") do |password|
118
+ @options[:sql_password] = password
119
+ end
120
+
121
+ # SQL Database
122
+ opts.on( '-sD', '--sql-database STRING', "SQL Database") do |database|
123
+ @options[:sql_adapter] = database
124
+ end
125
+ }
126
+ end
127
+
128
+ # Resets internal AMQP connection failure counter/interval
129
+ def amqp_reset_retry_interval
130
+ @amqp_retry_interval = @@AMQP_DEFAULT_RETRY_INTERVAL
131
+ @amqp_retry_attempt = 0
132
+ end
133
+
134
+ # Starts AMQP connection
135
+ def amqp_start
136
+ Bsl::Logger::Log "Starting AMQP - Connecting #{options[:amqp_user]}@#{options[:amqp_host]}:#{options[:amqp_port]}#{options[:amqp_vhost]}"
137
+ AMQP.start(:host => options[:amqp_host], :port => options[:amqp_port], :vhost => options[:amqp_vhost], :user => options[:amqp_user], :password => options[:amqp_password] ) do
138
+ amqp_reset_retry_interval()
139
+ end
140
+ end
141
+
142
+ # Stops Running AMQP connection
143
+ def amqp_stop
144
+ AMQP.stop { EM.stop }
145
+ end
146
+
147
+ # Connects to SQL
148
+ def sql_start
149
+ Bsl::Logger::Log "Connecting to SQL - Connecting #{options[:sql_adapter]}://#{options[:sql_user]}@#{options[:sql_host]}:#{options[:sql_port]}"
150
+
151
+
152
+ begin
153
+ ActiveRecord::Base.establish_connection(
154
+ {
155
+ :adapter => options[:sql_adapter],
156
+ :host => options[:sql_host],
157
+ :username => options[:sql_user],
158
+ :password => options[:sql_password],
159
+ :database => options[:sql_database]
160
+ }
161
+ )
162
+ @sql_connection = ActiveRecord::Base.connection
163
+ rescue Exception => e
164
+ Bsl::Logger::Log "Could not connect to DB, reason: #{e.message}!"
165
+ return false
166
+ end
167
+
168
+ return @sql_connection != nil
169
+ end
170
+
171
+ # Stop SQL connection
172
+ def sql_stop
173
+
174
+ end
175
+
176
+ # Handles failure when connecting to AMQP
177
+ def amqp_handle_failure(e)
178
+ amqp_stop()
179
+
180
+ max_attempts_reached = false
181
+ if(@@AMQP_MAX_RETRY_ATTEMPS != nil && @@AMQP_MAX_RETRY_ATTEMPS >= 0)
182
+ @amqp_retry_attempt = @amqp_retry_attempt + 1
183
+ max_attempts_reached = @amqp_retry_attempt > @@AMQP_MAX_RETRY_ATTEMPS
184
+ end
185
+
186
+ if(max_attempts_reached == false)
187
+ Bsl::Logger::Log "Unable to connect to AMQP: #{e.to_s}"
188
+ Bsl::Logger::Log "Next attempt in #{@amqp_retry_interval} sec(s)."
189
+
190
+ sleep (@amqp_retry_interval)
191
+ @amqp_retry_interval = @amqp_retry_interval * @@AMQP_RETRY_MULTIPLER
192
+ @amqp_retry_interval = @@AMQP_MAX_RETRY_INTERVAL if @amqp_retry_interval > @@AMQP_MAX_RETRY_INTERVAL
193
+ else
194
+ if(@@AMQP_MAX_RETRY_ATTEMPS != nil)
195
+ Bsl::Logger::Log "Maximum AQMP reconnect attempts limit reached (#{@@AMQP_MAX_RETRY_ATTEMPS}), quitting."
196
+ end
197
+ @request_exit = true
198
+ end
199
+ end
200
+
201
+ # Main entry-point
202
+ def main
203
+ super()
204
+
205
+ if(sql_start() == false)
206
+ Bsl::Logger::Log "Unable to connect to DB, quitting!"
207
+ exit
208
+ end
209
+
210
+ # Run loop while exit is not requested
211
+ while(@request_exit == false)
212
+ begin
213
+ amqp_start()
214
+ rescue Exception => e
215
+ amqp_handle_failure(e)
216
+ end
217
+ end
218
+
219
+ sql_stop()
220
+ end
221
+ end
222
+ end
223
+
224
+ if $0 == __FILE__
225
+ Thor::AppMaster.new.main
226
+ end
data/lib/ThorNode.rb ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $KCODE="UTF8"
4
+
5
+ # Options parsing gems
6
+ require 'optparse'
7
+ require 'pp'
8
+
9
+ # YAML
10
+ require 'yaml'
11
+
12
+ # Rubygems
13
+ require 'rubygems'
14
+
15
+ # Amqp Gems
16
+ require 'amqp'
17
+ #require 'mq'
18
+
19
+ # Event machine
20
+ require 'eventmachine'
21
+
22
+ require 'socket'
23
+
24
+ require 'digest/md5'
25
+
26
+ # Custom gems
27
+ require File.join(File.dirname(__FILE__), 'ThorApplication.rb')
28
+ require File.join(File.dirname(__FILE__), 'ThorUtils.rb')
29
+ require 'Bsl'
30
+
31
+ module Thor
32
+ class Node < Thor::Application
33
+ attr_accessor :request_exit, :jobs, :jobs_lock
34
+
35
+ StructJob = Struct.new(:klass, :path)
36
+
37
+ # C-tor
38
+ def initialize(opts = {})
39
+ super(opts)
40
+
41
+ @jobs = {}
42
+ @jobs_lock = Mutex.new
43
+
44
+ options[:jobs_dir] = File.join(File.dirname(__FILE__), 'jobs')
45
+ options[:boot_file] = ""
46
+
47
+ initialize_optparser { |opts|
48
+ ############################
49
+ # AMQP Section
50
+ ############################
51
+ # Jobs Dir
52
+ opts.on( '-j', '--jobs-dir STRING', "AMQP Virtual Host") do |jobs_dir|
53
+ options[:jobs_dir] = jobs_dir
54
+ end
55
+
56
+ # Boot file
57
+ opts.on( '-b', '--boot-file STRING', "Boot file specifying which jobs run automaticaly") do |boot_file|
58
+ options[:boot_file] = boot_file
59
+ end
60
+ }
61
+ end
62
+
63
+ def amqp_loop(amqp)
64
+ super(amqp)
65
+
66
+ # Start boot time jobs, move somewhere else!
67
+ boot_file = options[:boot_file]
68
+ if(boot_file != nil && boot_file != "")
69
+ boot_jobs = load_boot_file(boot_file)
70
+ if(boot_jobs != nil)
71
+ start_boot_time_jobs(boot_jobs, amqp)
72
+ end
73
+ end
74
+
75
+ #amqp_exchange_create_direct(guid) # Create local direct exchange
76
+ #amqp_exchange_connect_master(options[:amqp_channel_master], guid)
77
+ end
78
+
79
+ # Enumerate existing jobs
80
+ def enumerate_jobs(dir, exclude = [])
81
+ res_jobs = {}
82
+
83
+ if(File.directory?(dir) == false)
84
+ return nil
85
+ end
86
+
87
+ Dir[dir + "/*"].each do |path|
88
+ next if (File.directory?(path) == false)
89
+ basename = File.basename(path)
90
+ dash_index = basename.index("-")
91
+ if(dash_index == nil || dash_index < 0 || (basename.length - dash_index - 1) < 5)
92
+ Bsl::Logger::Log "Invalid job dir name - #{path}"
93
+ next
94
+ end
95
+
96
+ job_name = basename.slice(0,dash_index)
97
+ job_name_version = basename.slice(dash_index+1, basename.length - dash_index)
98
+
99
+ job_main = File.join(path + "/main.rb")
100
+ if(File.exists?(job_main) == false)
101
+ Bsl::Logger::Log "Job entry point does not exists - #{job_main}"
102
+ next
103
+ end
104
+
105
+ require job_main
106
+
107
+ job = nil
108
+ begin
109
+ job = Thor::Jobs::const_get(job_name)
110
+ rescue
111
+ Bsl::Logger::Log "Unable to find class 'Thor::Jobs::#{job_name}' in file '#{job_main}'"
112
+ next
113
+ end
114
+
115
+ job_author = job.author
116
+ job_description = job.description
117
+ job_license = job.license
118
+ job_version = job.version
119
+
120
+ if(job_version != job_name_version)
121
+ Bsl::Logger::Log "Job '#{job_name}' inconsistent versions, filename: '#{job_name_version}', class: '#{job_version}, skipping.'"
122
+ next
123
+ end
124
+
125
+ Bsl::Logger::Log "Adding job, name: '#{job_name}', version: '#{job_version}', basename: '#{basename}'"
126
+ res_jobs[basename] = StructJob.new(job, job_main)
127
+ end
128
+
129
+ return res_jobs
130
+ end
131
+
132
+ # Load boot file if specified
133
+ def load_boot_file(boot_file)
134
+ Bsl::Logger::Log "Loading boot file '#{boot_file}'."
135
+ if(File.exists?(boot_file) == false)
136
+ Bsl::Logger::Log "Boot file '#{boot_file}' is not valid file path!"
137
+ return nil
138
+ end
139
+
140
+ require boot_file
141
+ basename = File.basename(boot_file, ".*")
142
+ return ThorBoot::const_get(basename)::boot_jobs
143
+ end
144
+
145
+ # Starts boot time jobs
146
+ def start_boot_time_jobs(boot_time_jobs, amqp)
147
+ boot_time_jobs.each do |boot_time_job|
148
+ @jobs_lock.synchronize {
149
+ job_name = boot_time_job[:name]
150
+ job = @jobs[job_name]
151
+ if(job != nil)
152
+ job_klass = job[:klass]
153
+
154
+ job_opts = options
155
+ job_opts = job_opts.merge(boot_time_job[:options])
156
+ job_opts = job_opts.merge({:amqp => {:conn => amqp}})
157
+ job_opts.merge!({:amqp => {:conn => amqp}}) # TODO: Use Thor::AmqpNode defaults
158
+
159
+ if(options[:verbose])
160
+ Bsl::Logger::Log "Starting boot job '#{job_klass.name}'."
161
+ #Bsl::Logger::Log "Job options '#{job_opts.inspect}'."
162
+ end
163
+
164
+ job_instance = job_klass.new(job_opts)
165
+ job_instance.start(job_opts)
166
+ Bsl::Logger::Log "Boot job '#{job_klass.name}' started."
167
+ else
168
+ Bsl::Logger::Log "Invalid boot job specified, name: '#{job_name}'"
169
+ end
170
+ }
171
+ end
172
+ end
173
+
174
+ # Main entry-point
175
+ def main
176
+ super()
177
+
178
+ # Refresh jobs
179
+ new_jobs = enumerate_jobs(options[:jobs_dir])
180
+ @jobs_lock. synchronize {
181
+ @jobs = new_jobs
182
+ }
183
+
184
+ # Run loop while exit is not requested
185
+ while(@request_exit == false)
186
+ begin
187
+ amqp_start()
188
+ rescue SystemExit, Interrupt
189
+ Bsl::Logger::Log "Received interrupt, quitting!"
190
+ @request_exit = true
191
+ amqp_stop()
192
+ rescue Exception => e
193
+ amqp_handle_failure(e)
194
+ end
195
+ puts "."
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ if $0 == __FILE__
202
+ Thor::Node.new.main
203
+ end
data/lib/ThorUtils.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'Bsl'
2
+
3
+ require 'sys/admin'
4
+ require 'sys/proctable'
5
+ require 'sys/uname'
6
+ require 'sys/cpu'
7
+ require 'sys/uptime'
8
+ require 'net/proto'
9
+ require 'sys/host'
10
+ require 'sys/filesystem'
11
+
12
+ module Thor
13
+
14
+ # Loads all ActiveRecord models
15
+ def self.require_all_models(logit = false)
16
+ dir = File.join(File.dirname(__FILE__), 'models/')
17
+ Dir[dir + '*.rb'].each do |file|
18
+ if(logit)
19
+ Bsl::Logger::Log "Including model '#{file}'"
20
+ end
21
+ require file
22
+ end
23
+ end
24
+
25
+ # Generates unique exchange name
26
+ # Format is following IP.HOSTNAME.PID.TID(INST_HASH)
27
+ # PID = Process ID
28
+ # TID = Thread ID
29
+ # INST_HASH = Hash from instance pointer if some instance is specified as param
30
+ def self.generate_guid(inst = nil)
31
+ res = ""
32
+
33
+ ip = Sys::Host.ip_addr.to_s.gsub(".", "-")
34
+ hostname = Sys::Host.hostname.gsub(".", "--")
35
+ pid = Process.pid
36
+ tid = Thread.current.object_id
37
+ instance = inst != nil ? ".#{instance.to_s}" : ""
38
+ return "#{ip.to_s}.#{hostname}.#{pid}.#{tid}#{instance}"
39
+ end
40
+ end
data/lib/ThorWorker.rb ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $KCODE="UTF8"
4
+
5
+ # Options parsing gems
6
+ require 'optparse'
7
+ require 'pp'
8
+
9
+ # YAML
10
+ require 'yaml'
11
+
12
+ # Rubygems
13
+ require 'rubygems'
14
+
15
+ # Event machine
16
+ require 'eventmachine'
17
+
18
+ # Custom gems
19
+ require File.join(File.dirname(__FILE__), 'ThorApplication.rb')
20
+ require 'Bsl'
21
+
22
+ module Thor
23
+ class AppWorker < Thor::Application
24
+ attr_accessor :options, :optpars
25
+
26
+ def initialize(opts = {})
27
+ super(opts)
28
+
29
+ # Event Machine options
30
+ options[:em_port] = 8467 # Thor on cell-phone keyboard
31
+ options[:em_auth_token] = ""
32
+
33
+ initialize_optparser { |opts|
34
+ ############################
35
+ # EventMachine Section
36
+ ############################
37
+ # EM Port
38
+ opts.on( '-ep', '--em-port NUM', "EventMachine port") do |port|
39
+ options[:em_port] = port
40
+ end
41
+
42
+ # EM Authentication token
43
+ opts.on( '-eat', '--em-auth-token STRING', "Authentication token used for communication with EM") do |token|
44
+ options[:em_auth_token] = token
45
+ end
46
+ }
47
+ end
48
+
49
+ # Main entry-point
50
+ def main
51
+ super()
52
+ end
53
+ end
54
+ end
55
+
56
+ if $0 == __FILE__
57
+ Thor::AppWorker.new.main
58
+ end
data/lib/boot/Boot.rb ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $KCODE="UTF8"
4
+
5
+ module ThorBoot
6
+ class Boot
7
+ # Default AMQP Options
8
+ @@DEFAULT_AMQP = {
9
+ :amqp => {
10
+ :host => "amqp_host",
11
+ :port => 5672,
12
+ :user => "amqp_user",
13
+ :password => "amqp_password",
14
+ :vhost => "amqp_vhost"
15
+ }
16
+ }
17
+
18
+ # Default EventMachine Options
19
+ @@DEFAULT_EM = {
20
+
21
+ }
22
+
23
+ # Default SQL Options
24
+ @@DEFAULT_SQL = {
25
+ :sql => {
26
+ :host => "sql_host",
27
+ :port => 5432,
28
+ :user => "sql_user",
29
+ :password => "sql_password",
30
+ :schema => "public"
31
+ }
32
+ }
33
+
34
+ # Return default AMQP Options
35
+ def self.default_options_amqp
36
+ return @@DEFAULT_AMQP
37
+ end
38
+
39
+ # Returns default Event Machine Options
40
+ def self.default_options_em
41
+ return @@DEFAULT_EM
42
+ end
43
+
44
+ # Returns default SQL Options
45
+ def self.default_options_sql
46
+ return @@DEFAULT_SQL
47
+ end
48
+
49
+ # Return merged default Options
50
+ def self.default_options
51
+ res = {}
52
+
53
+ res.merge!(default_options_amqp)
54
+ res.merge!(default_options_em)
55
+ res.merge!(default_options_sql)
56
+
57
+ return res
58
+ end
59
+
60
+ # Merges options with default options
61
+ def self.merge_options(opts)
62
+ return default_options.merge(opts)
63
+ end
64
+
65
+ # Default function for getting boot jobs
66
+ def self.boot_jobs
67
+ boot_jobs = []
68
+ return boot_jobs
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $KCODE="UTF8"
4
+
5
+ require File.join(File.dirname(__FILE__), 'Node.rb')
6
+
7
+ module ThorBoot
8
+ class Client < Node
9
+ def self.boot_jobs
10
+ opts_node = {:channel_nodes => "clients"}
11
+ opts_client = {}
12
+ opts_monitor_client = {
13
+ :channel_request => "monitor.request",
14
+ :channel_response => "monitor.response"
15
+ }
16
+ boot_jobs = [
17
+ {:name => "Node-0.0.1", :options => merge_options(opts_node)},
18
+ {:name => "Client-0.0.1", :options => merge_options(opts_client)},
19
+ {:name => "MonitorClient-0.0.1", :options => merge_options(opts_monitor_client)}
20
+ ]
21
+ return (super() + boot_jobs)
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $KCODE="UTF8"
4
+
5
+ require File.join(File.dirname(__FILE__), 'Node.rb')
6
+
7
+ module ThorBoot
8
+ class Master < Node
9
+ def self.boot_jobs
10
+ opts_node = {:channel_nodes => "masters"}
11
+ opts_master = {}
12
+ opts_monitor_master = {
13
+ :channel_request => "monitor.request",
14
+ :channel_response => "monitor.response",
15
+ :request_interval => 1
16
+ }
17
+ boot_jobs = [
18
+ {:name => "Node-0.0.1", :options => merge_options(opts_node)},
19
+ {:name => "Master-0.0.1", :options => merge_options(opts_master)},
20
+ {:name => "MonitorMaster-0.0.1", :options => merge_options(opts_monitor_master)}
21
+ ]
22
+ return (super() + boot_jobs)
23
+ end
24
+ end
25
+ end
26
+
data/lib/boot/Node.rb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $KCODE="UTF8"
4
+
5
+ require File.join(File.dirname(__FILE__), 'Boot.rb')
6
+
7
+ module ThorBoot
8
+ class Node < Boot
9
+ def self.boot_jobs
10
+ opts = {:channel_nodes => "nodes"}
11
+ boot_jobs = [{:name => "Node-0.0.1", :options => merge_options(opts)}]
12
+ return (super() + boot_jobs)
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,12 @@
1
+ verbose: false
2
+
3
+ amqp_host: your-host.com
4
+ amqp_port: 5672
5
+ amqp_user: your_user
6
+ amqp_password: your_password
7
+ amqp_vhost: "your_vhost
8
+
9
+ amqp_channel_master: "master"
10
+
11
+ em_port: 8467
12
+ em_auth_token: ""
@@ -0,0 +1,4 @@
1
+ verbose: false
2
+
3
+ em_port: 8467
4
+ em_auth_token: ""
@@ -0,0 +1,19 @@
1
+ verbose: false
2
+
3
+ amqp_host: "host"
4
+ amqp_port: 5672
5
+ amqp_user: "user"
6
+ amqp_password: "password"
7
+ amqp_vhost: "vhost"
8
+
9
+ sql_adapter: "postgresql"
10
+ sql_host: "host"
11
+ sql_port: 5432
12
+ sql_user: "user"
13
+ sql_password: "password"
14
+ sql_database: "db"
15
+
16
+ amqp_channel_master: "master"
17
+
18
+ em_port: 8467
19
+ em_auth_token: ""