freyr 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.irbrc CHANGED
@@ -1,4 +1,5 @@
1
1
  require File.dirname(__FILE__) + '/lib/freyr.rb'
2
+ Freyr.logger = Logger.new(STDOUT)
2
3
  include Freyr
3
4
 
4
5
  Service.add_file("Freyrfile")
data/Freyrfile CHANGED
@@ -1,7 +1,21 @@
1
1
  service :sleep do
2
+ group :sleepers
3
+
4
+ also_as :short_sleep
5
+ start 'sleep 20'
6
+ # rvm 'ree-1.8.7-2011.03@foobar'
7
+
8
+ proc_match /sleep 20/
9
+ end
10
+
11
+ service :sleep2 do
12
+ requires :sleep, :sudosleep
2
13
  start 'sleep 20'
14
+ proc_match /sleep 20/
3
15
  end
4
16
 
5
17
  service :sudosleep do
18
+ group :sleepers
6
19
  start 'sudo sleep 20'
20
+ proc_match /sleep 20/
7
21
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.2
1
+ 0.5.0
data/bin/freyr CHANGED
@@ -4,4 +4,6 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
4
  require 'freyr'
5
5
  require 'freyr/cli'
6
6
 
7
+ trap("SIGINT") { exit(false) }
8
+
7
9
  Freyr::CLI.start
@@ -5,7 +5,7 @@ module Freyr
5
5
  super
6
6
 
7
7
  if options.trace?
8
- Freyr.logger = Logger.new(STDOUT)
8
+ Freyr.logger.level = Logger::DEBUG
9
9
  end
10
10
 
11
11
  get_services
@@ -21,18 +21,19 @@ module Freyr
21
21
 
22
22
  groups_join = '|'
23
23
 
24
- lengths = Service.s.collect do |s|
24
+ lengths = {}
25
+ Service.s.each do |name,s|
25
26
  n = " #{s.name}(#{s.groups.join(groups_join)})"
26
27
  max_length = n.length if n.length > max_length
27
- n.length
28
+ lengths[name] = n.length
28
29
  end
29
30
 
30
31
  max_length += 3 # min distance between name and group
31
32
 
32
33
  strs = []
33
- Service.s.each_with_index do |s,i|
34
+ Service.s.each do |i,s|
34
35
  str = ' '
35
- if s.sudo
36
+ if s.info.sudo
36
37
  str << set_color('*', :yellow)
37
38
  else
38
39
  str << ' '
@@ -86,11 +87,11 @@ module Freyr
86
87
 
87
88
  if args[:procinfo]
88
89
  begin
89
- pid = s.command.pid
90
+ pid = s.pid_file.pid
90
91
 
91
92
  proc = ProcessInfo[pid]
92
93
 
93
- str << " CPU: #{proc.pcpu}% - MEM: #{proc.mem_in_mb.to_i}mb" if proc
94
+ str << " CPU: #{proc.pcpu}% - MEM: #{proc.mem_in_mb.to_i}mb PID: #{pid}" if proc
94
95
  # rescue => e
95
96
  end
96
97
  end
@@ -108,24 +109,19 @@ module Freyr
108
109
  end
109
110
 
110
111
  def get_from_name name
111
- group = ServiceGroup.new
112
-
113
- unless name
114
- s = Service.s.find {|svc| svc.dir == Dir.pwd}
115
- Freyr.logger.debug('getting service from directory') {"in #{Dir.pwd} found service: #{s.inspect}"}
116
- return group << s if s
117
- end
118
-
119
- if options.namespace && s = Service["#{options.namespace}:#{name}"].first
120
- group << s # only pickng one because if it's namespaced it's not a group
112
+ if name
113
+ group = Service[name.to_sym]
114
+ Freyr.logger.debug('find service by name') {"#{name.to_sym} #{group.inspect}"}
115
+ group
121
116
  else
122
- Service[name].each do |s|
123
- group << s
117
+ if s = Service.by_dir[Dir.pwd]
118
+ Freyr.logger.debug('getting service from directory') {"in #{Dir.pwd} found service: #{s.inspect}"}
119
+ else
120
+ Freyr.logger.debug('getting service from directory') {"in #{Dir.pwd} unable to find any service"}
124
121
  end
122
+
123
+ Service[s.name]
125
124
  end
126
-
127
- Freyr.logger.debug('getting service') {group.inspect}
128
- group
129
125
  end
130
126
 
131
127
  def get_services
@@ -5,9 +5,9 @@ module Freyr
5
5
  desc 'start [SERVICE=dirname]', 'Start particular service'
6
6
  def start(name=nil)
7
7
  services = get_from_name(name)
8
- if !services.empty?
8
+ if services && !services.empty?
9
9
  Freyr.logger.debug('starting services') {services.inspect}
10
- names = services.collect {|s| s.name}
10
+ names = services.call_order.collect {|s| s.name}
11
11
  say "Starting the " << set_color(names.join(', '), :blue) << ' services'
12
12
 
13
13
  changed_names = services.run
@@ -15,15 +15,20 @@ module Freyr
15
15
  list_all_services(:highlight_state => changed_names).each {|l| say(l)}
16
16
  else
17
17
  say "Can't find the #{name} service", :red
18
+ exit(false)
18
19
  end
19
20
  rescue AdminRequired
20
21
  say "Please run in sudo to launch #{name}.", :red
22
+ exit(false)
23
+ rescue Service::MissingDependency => e
24
+ say "Missing dependency, could not launch service: #{e.to_s}"
25
+ exit(false)
21
26
  end
22
27
 
23
28
  desc 'stop [SERVICE=dirname]', 'Stop particular service'
24
29
  def stop(name=nil)
25
30
  services = get_from_name(name)
26
- if !services.empty?
31
+ if services && !services.empty?
27
32
  Freyr.logger.debug('stopping services') {services.inspect}
28
33
  names = services.collect {|s| s.name}
29
34
  say "Stopping the " << set_color(names.join(', '), :blue) << ' services'
@@ -33,15 +38,17 @@ module Freyr
33
38
  list_all_services(:highlight_state => changed_names).each {|l| say(l)}
34
39
  else
35
40
  say "Can't find the #{name} service", :red
41
+ exit(false)
36
42
  end
37
43
  rescue AdminRequired
38
44
  say "Please run in sudo to stop #{name}.", :red
45
+ exit(false)
39
46
  end
40
47
 
41
48
  desc 'restart [SERVICE=dirname]', 'restart particular service'
42
49
  def restart(name=nil)
43
50
  services = get_from_name(name)
44
- if !services.empty?
51
+ if services && !services.empty?
45
52
  Freyr.logger.debug('restarting services') {services.inspect}
46
53
  say "Restarting the " << set_color(services.collect {|s| s.name}.join(', '), :blue) << ' services'
47
54
 
@@ -50,11 +57,13 @@ module Freyr
50
57
  list_all_services(:highlight_state => names).each {|l| say(l)}
51
58
  else
52
59
  say "Can't find the #{name} service", :red
60
+ exit(false)
53
61
  end
54
62
 
55
63
  rescue AdminRequired
56
64
  say "Please run in sudo to launch #{name}.", :red
65
+ exit(false)
57
66
  end
58
67
 
59
68
  end
60
- end
69
+ end
@@ -1,21 +1,21 @@
1
1
  module Freyr
2
2
  class CLI < Thor
3
3
 
4
- desc 'update_pid [SERVICE=dirname]', 'Update pid from proc_match (good to use if service already launched)'
5
- def update_pid(name=nil)
6
- services = get_from_name(name)
7
- if s = services.first
8
- if pid = s.command.update_pid
9
- say "Updated pid for "<< set_color(s.name,:blue) << ' to ' << set_color(pid,:red)
10
- elsif s.proc_match
11
- say "Couldn't find pid for process matcher #{s.proc_match.inspect}", :red
12
- else
13
- say "Service #{s.name} doesn't have a value for proc_match set.", :red
14
- end
15
- else
16
- say "Couldn't find service with name #{name}.", :red
17
- end
18
- end
4
+ # desc 'update_pid [SERVICE=dirname]', 'Update pid from proc_match (good to use if service already launched)'
5
+ # def update_pid(name=nil)
6
+ # services = get_from_name(name)
7
+ # if s = services.first
8
+ # if pid = s.command.update_pid
9
+ # say "Updated pid for "<< set_color(s.name,:blue) << ' to ' << set_color(pid,:red)
10
+ # elsif s.proc_match
11
+ # say "Couldn't find pid for process matcher #{s.proc_match.inspect}", :red
12
+ # else
13
+ # say "Service #{s.name} doesn't have a value for proc_match set.", :red
14
+ # end
15
+ # else
16
+ # say "Couldn't find service with name #{name}.", :red
17
+ # end
18
+ # end
19
19
 
20
20
  end
21
21
  end
data/lib/freyr/command.rb CHANGED
@@ -3,96 +3,22 @@ module Freyr
3
3
  class Command
4
4
  extend Forwardable
5
5
 
6
- ROOT_PIDS = '/var/run/freyr'
7
- USER_PIDS = File.expand_path('.freyr', '~')
6
+ attr_reader :name, :service
8
7
 
9
- if !File.exist?(USER_PIDS)
10
- Dir.mkdir(USER_PIDS)
11
- elsif !File.directory?(USER_PIDS)
12
- File.delete(USER_PIDS)
13
- Dir.mkdir(USER_PIDS)
8
+ def initialize(service, command=nil, args = {})
9
+ @service = service
14
10
  end
15
-
16
- attr_reader :command, :name, :service
17
-
18
- def initialize(name, command=nil, args = {})
19
- if name.is_a?(Service)
20
- @service = name
21
- @name = service.name
22
- @command = service.start_command
23
- @env = service.env
24
- else
25
- @name = name
26
- @command = command
27
-
28
- @env = args[:env]
29
- end
30
- end
31
-
32
- def pid_file
33
- path = File.join(file_dir,"#{@name}.pid")
34
- if File.exist?(path)
35
- path
36
- else
37
- File.join(file_dir(true),"#{@name}.pid")
38
- end
39
- end
40
-
41
- def file_dir(force_user = false)
42
- return USER_PIDS if force_user
43
- admin? ? ROOT_PIDS : USER_PIDS
44
- end
45
-
46
- def read_pid
47
- return unless File.exist?(pid_file)
48
- p = File.open(pid_file).read
49
- p ? p.to_i : nil
50
- end
51
-
52
- def pid(force = false)
53
- @pid = nil if force
54
- @pid ||= read_pid
55
- end
56
-
57
- def alive?
58
- return unless pid
59
- Process.getpgid(pid)
60
- true
61
- rescue Errno::ESRCH
62
- retried = false unless defined?(retried)
63
-
64
- if !retried && @pid = pid_from_procname
65
- save
66
- retried = true
67
- retry
68
- end
69
-
70
- if pid(true)
71
- File.delete(pid_file) unless admin? && !is_root?
72
- end
73
-
74
- false
75
- end
76
-
77
- def delete_if_dead
78
- !alive?
79
- end
80
-
81
- def save
82
- if File.exist?(pid_file)
83
- old_pid = read_pid
84
- begin
85
- Process.kill('KILL', old_pid) if old_pid && old_pid.to_s != @pid.to_s
86
- rescue Errno::ESRCH
87
- end
88
- end
89
-
90
- File.open(pid_file, 'w') {|f| f.write(@pid)}
11
+
12
+ def_delegators :'@service', :info
13
+ def_delegators :info, :name, :env
14
+
15
+ def command
16
+ info.start
91
17
  end
92
18
 
93
19
  def run!
94
20
  return unless command
95
- kill! if alive?
21
+ kill! if service.alive?
96
22
 
97
23
  require_admin
98
24
 
@@ -101,90 +27,31 @@ module Freyr
101
27
  pid = spawn(command)
102
28
 
103
29
  Freyr.logger.debug("attempting to run command") {command.inspect}
30
+ str = "\nStarting #{info.name} with #{command.inspect}"
31
+ OUT.puts '',"Starting #{info.name} with #{command.inspect}", '='*str.length
32
+ Process.detach(pid)
104
33
 
105
- @pid = pid
106
-
107
- Process.detach(@pid)
108
-
109
- if proc_match
110
- puts "Waiting for pid from match of #{proc_match.inspect}"
111
-
112
- start = Time.now
113
- until (pid = pid_from_procname) || (Time.now-start) > 40
114
- print '.'; STDOUT.flush
115
- sleep(0.2)
116
- end
117
-
118
- raise "\nCouldnt find pid after 40 seconds" unless pid
119
-
120
- puts '*'
121
-
122
- @pid = pid
123
- end
34
+ pid = service.pid_file.wait_for_pid
124
35
 
125
- puts "PID of new #{name} process is #{@pid}"
126
- save
36
+ OUT.puts "PID of new #{info.name} process is #{pid}"
127
37
 
128
- if ping
129
- pinger = Pinger.new(self)
130
-
131
- puts '',"Waiting for response from #{pinger.url}"
38
+ if info.ping
39
+ pinger = Pinger.new(@service)
132
40
 
133
- # Move this pinger code somewhere else
134
- start = Time.now
135
- begin
136
- print '.'; STDOUT.flush
137
- pinger.ping
138
- sleep(0.6)
139
- end until pinger.server_probably_launched? || (Time.now-start) > 40 || !alive?
140
-
141
- if alive?
142
-
143
- if pinger.response
144
- puts '*',"Last response recieved with code #{pinger.response.code}"
145
- else
146
- puts 'x',"Couldn't reach #{name} service"
147
- end
148
- else
149
- puts 'x', "Service died durring launch"
150
- end
41
+ pinger.wait_for_resp { @service.alive? }
151
42
  end
152
43
 
153
- if alive?
154
- puts "Launch took about #{(Time.now-total_time).ceil} seconds"
155
-
156
- @pid
44
+ if @service.alive?
45
+ OUT.puts "Launch took about #{(Time.now-total_time).ceil} seconds"
46
+ pid
157
47
  else
158
- puts "#{name} service wasn't launched correctly. For details see: #{log}"
159
- delete_if_dead
160
- end
161
- end
162
-
163
- def update_pid
164
- if @pid = pid_from_procname
165
- save
166
- @pid
167
- end
168
- end
169
-
170
- def pid_from_procname
171
- return unless proc_match
172
-
173
- pids = `ps -eo pid,command`.split("\n").inject({}) do |r, pid|
174
- if m = pid.match(/^\s*(\d+)\s(.+)$/)
175
- r[m[2]] = m[1].to_i
176
- end
177
- r
178
- end
179
-
180
- if procline = pids.keys.find {|p| p.match(proc_match)}
181
- pids[procline]
48
+ OUT.puts "#{info.name} service wasn't launched correctly. For details see: #{info.log}"
182
49
  end
183
50
  end
184
51
 
185
52
  def kill!(sig=nil)
186
53
  require_admin
187
- sig ||= stop_sig || 'KILL'
54
+ sig ||= info.stop_sig || 'KILL'
188
55
 
189
56
  Freyr.logger.debug("sending signal to process") {"Signal: #{sig}, PID: #{pid}"}
190
57
 
@@ -192,64 +59,71 @@ module Freyr
192
59
  Process.kill(sig, pid)
193
60
  end
194
61
  end
62
+
63
+ def pid force = false
64
+ @service.pid_file.pid(force)
65
+ end
195
66
 
196
67
  def restart!
197
68
  require_admin
198
69
 
199
- if restart
70
+ if info.restart
200
71
  chdir
201
- system(restart)
202
- update_pid
203
- elsif restart_sig
204
- kill!(restart_sig)
72
+ system(info.restart)
73
+ elsif info.restart_sig
74
+ kill!(info.restart_sig)
205
75
  else
206
76
  run!
207
77
  end
208
78
  end
209
79
 
210
- def admin?
211
- sudo
212
- end
213
-
214
80
  private
215
81
 
216
82
  def require_admin
217
- raise AdminRequired if admin? && !is_root?
218
- end
219
-
220
- def is_root?
221
- ENV['USER'] == 'root'
83
+ raise AdminRequired if info.sudo && !Freyr.is_root?
222
84
  end
223
85
 
224
86
  def chdir
225
- Dir.chdir File.expand_path(dir||'/')
87
+ Dir.chdir File.expand_path(info.dir||'/')
226
88
  end
227
89
 
228
90
  def spawn(command)
91
+ if info.rvm && RVM.installed?
92
+ Freyr.logger.debug('attempting to set rvm') {info.rvm}
93
+ if RVM.installed?(info.rvm)
94
+ command = "rvm #{info.rvm} exec #{command}"
95
+ Freyr.logger.debug('changed command to') {command}
96
+ else
97
+ abort("must setup rvm correctly, run: rvm --install --create #{info.rvm}")
98
+ end
99
+ elsif info.rvm
100
+ Freyr.logger.debug("rvm not installed so can't switch to") {info.rvm}
101
+ end
102
+
229
103
  fork do
230
- File.umask self.umask if self.umask
231
- uid_num = Etc.getpwnam(self.uid).uid if uid
232
- gid_num = Etc.getgrnam(self.gid).gid if gid
104
+ File.umask info.umask if info.umask
105
+ uid_num = Etc.getpwnam(info.uid).uid if info.uid
106
+ gid_num = Etc.getgrnam(info.gid).gid if info.gid
233
107
 
234
- ::Dir.chroot(self.chroot) if self.chroot
108
+ ::Dir.chroot(info.chroot) if info.chroot
235
109
  ::Process.setsid
236
- ::Process.groups = [gid_num] if self.gid
237
- ::Process::Sys.setgid(gid_num) if self.gid
238
- ::Process::Sys.setuid(uid_num) if self.uid
110
+ ::Process.groups = [info.guid] if info.gid
111
+ ::Process::Sys.setgid(info.guid) if info.gid
112
+ ::Process::Sys.setuid(info.guid) if info.uid
239
113
  chdir
240
114
  $0 = "freyr - #{name} (#{command})"
241
115
  STDIN.reopen "/dev/null"
242
- if log_cmd
116
+ if info.log_cmd
243
117
  STDOUT.reopen IO.popen(log_cmd, "a")
244
- elsif log && service.write_log?
245
- STDOUT.reopen log, "a"
118
+ elsif info.log && !info.dont_write_log
119
+ STDOUT.reopen info.log, "a"
246
120
  else
247
121
  STDOUT.reopen "/dev/null"
248
122
  end
249
- if err_log_cmd
250
- STDERR.reopen IO.popen(err_log_cmd, "a")
251
- elsif err_log && (log_cmd || err_log != log)
252
- STDERR.reopen err_log, "a"
123
+ if info.err_log_cmd
124
+ STDERR.reopen IO.popen(info.err_log_cmd, "a")
125
+ elsif info.err_log && (info.log_cmd || info.err_log != info.log)
126
+ STDERR.reopen info.err_log, "a"
253
127
  else
254
128
  STDERR.reopen STDOUT
255
129
  end
@@ -263,18 +137,13 @@ module Freyr
263
137
  end
264
138
  end
265
139
 
266
- exec(command) unless command.empty?
140
+ exec(command)
267
141
  end
268
142
  end
269
143
 
270
144
 
271
145
  class << self
272
146
  def add_service_method *methods
273
- methods.each do |meth|
274
- define_method(meth) do |*args|
275
- @service.__send__(meth,*args) if @service
276
- end
277
- end
278
147
  end
279
148
  end
280
149
  end
@@ -0,0 +1,10 @@
1
+ module Freyr
2
+ def is_root?
3
+ Process.euid == 0
4
+ end
5
+
6
+ def has_rvm?
7
+ ENV["rvm_loaded_flag"] == "1"
8
+ end
9
+ class Timeout < StandardError; end
10
+ end
@@ -0,0 +1,73 @@
1
+ module Freyr
2
+ class PidFile
3
+ def initialize path, procname=nil
4
+ @path = path
5
+ @procname = procname
6
+ end
7
+
8
+ def process_info
9
+ @proces_info ||= ProcessInfo.new(@pid) if @pid
10
+ end
11
+
12
+ def alive?
13
+ return unless pid
14
+ Process.getpgid(pid)
15
+ true
16
+ rescue Errno::ESRCH
17
+ false
18
+ end
19
+
20
+ def pid_from_file
21
+ return unless File.exist?(@path)
22
+ p = File.open(@path).read.chomp
23
+ p ? p.to_i : nil
24
+ end
25
+
26
+ def pid force=false
27
+ if !force && File.exist?(@path)
28
+ pid_from_file
29
+ elsif @procname
30
+ pid_from_procname
31
+ else
32
+ pid_from_file
33
+ end
34
+ end
35
+
36
+ def pid_from_procname force=false
37
+ pids = PidFile.pid_command_hash(force)
38
+
39
+ if procline = pids.keys.find {|p| p.match(@procname)}
40
+ pids[procline]
41
+ end
42
+ end
43
+
44
+ def wait_for_pid wait = 40, interval=0.2
45
+ OUT.puts "Waiting #{wait}s for pid from match of #{@procname}"
46
+
47
+ start = Time.now
48
+
49
+ until (pid = pid_from_procname(true)) || (Time.now-start) > wait
50
+ OUT.print '.';OUT.flush
51
+ sleep(interval)
52
+ end
53
+
54
+ raise Timeout, "\n Couldn't find pid after" unless pid
55
+ OUT.puts '*'
56
+ pid
57
+ end
58
+
59
+ class << self
60
+
61
+ def pid_command_hash force=false
62
+ @pid_command_hash = nil if force
63
+ @pid_command_hash ||= `ps -eo pid,command`.split("\n").inject({}) do |r, pid|
64
+ if m = pid.match(/^\s*(\d+)\s(.+)$/)
65
+ r[m[2]] = m[1].to_i
66
+ end
67
+ r
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
data/lib/freyr/pinger.rb CHANGED
@@ -9,9 +9,32 @@ module Freyr
9
9
  # Response object
10
10
  attr_reader :response
11
11
 
12
- def initialize(command)
13
- @command = command
14
- @url = command.ping
12
+ def initialize(service)
13
+ @service = service
14
+ @url = service.info.ping
15
+ end
16
+
17
+ def wait_for_resp wait=40, interval = 0.6, &blk
18
+ OUT.puts "\nWaiting for response from #{url}"
19
+ start = Time.now
20
+
21
+ blk ||= lambda {true}
22
+
23
+ begin
24
+ OUT.print '.'; OUT.flush
25
+ ping
26
+ sleep(interval)
27
+ end until server_probably_launched? || (Time.now-start) > wait || !blk.call
28
+
29
+ if blk.call
30
+ if response
31
+ OUT.puts '*', "Last response received with code #{response.code}"
32
+ else
33
+ OUT.puts 'x', "Couldn't reach #{@service.name} service"
34
+ end
35
+ else
36
+ OUT.puts 'x',"Service died durring launch"
37
+ end
15
38
  end
16
39
 
17
40
  # The URI object for the given URL
@@ -35,7 +58,7 @@ module Freyr
35
58
 
36
59
  # Did the response recieve a success http code
37
60
  def success?
38
- response.is_a?(Net::HTTPSuccess)
61
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
39
62
  end
40
63
 
41
64
  # Did the response recieve a 500 error
@@ -43,7 +66,7 @@ module Freyr
43
66
  response.is_a?(Net::HTTPInternalServerError)
44
67
  end
45
68
 
46
- # Did it recieve 2xx or 500
69
+ # Did it receive 2xx or 500
47
70
  def server_probably_launched?
48
71
  success? || server_error?
49
72
  end