freyr 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,19 +4,28 @@ module Freyr
4
4
  def initialize pid
5
5
  @pid = pid
6
6
  end
7
+
8
+ def alive?
9
+ Process.getpgid(pid)
10
+ true
11
+ rescue Errno::ESRCH
12
+ false
13
+ end
7
14
 
8
15
  def ps
9
16
  return if !pid || pid.to_s.empty?
10
- info = `ps p #{pid} -o pid,rss,vsz,pmem,pcpu,ruser,command`
11
- match = info.match(/#{pid}\s+(\d+)\s+(\d+)\s+([\d\.]+)\s+([\d\.]+)\s+(\w+)\s+(.+)/)
12
- return unless match
13
- @rss = match[1].to_i
14
- @vsz = match[2].to_i
15
- @pmem = match[3].to_f
16
- @pcpu = match[4].to_f
17
- @ruser = match[5]
18
- @command = match[6]
19
- info
17
+ @ps_info ||= begin
18
+ info = `ps p #{pid} -o pid,rss,vsz,pmem,pcpu,ruser,command`
19
+ match = info.match(/#{pid}\s+(\d+)\s+(\d+)\s+([\d\.]+)\s+([\d\.]+)\s+(\w+)\s+(.+)/)
20
+ return unless match
21
+ @rss = match[1].to_i
22
+ @vsz = match[2].to_i
23
+ @pmem = match[3].to_f
24
+ @pcpu = match[4].to_f
25
+ @ruser = match[5]
26
+ @command = match[6]
27
+ info
28
+ end
20
29
  end
21
30
 
22
31
  def port
data/lib/freyr/rvm.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Freyr
2
+ module RVM
3
+ extend self
4
+
5
+ def installed? ruby=nil
6
+ return !`which rvm`.empty? unless ruby
7
+
8
+ ruby, gemset = ruby.split('@')
9
+ if rubies.include? ruby
10
+ gemset ? gemsets_for(ruby).include?(gemset) : true
11
+ else
12
+ false
13
+ end
14
+ end
15
+
16
+ def rubies
17
+ @rubies ||= `rvm list`.strip.split("\n").collect do |line|
18
+ next unless line =~ /^(\s{3}|=)/
19
+ line.strip.sub(/\=\>\s/,'').sub(/\s\[.+\]$/,'')
20
+ end.compact
21
+ end
22
+
23
+ def gemsets_for ruby
24
+ @gemsets_for ||= Hash.new do |h,ruby|
25
+ output = `rvm #{ruby} exec rvm gemset list`.strip.split("\n")
26
+ output.shift
27
+ h[ruby] = output.collect do |line|
28
+ next unless line =~ /^(\s{3}|=)/
29
+ line.strip.sub(/\=\>\s/,'')
30
+ end.compact
31
+ end
32
+ @gemsets_for[ruby]
33
+ end
34
+ end
35
+ end
data/lib/freyr/service.rb CHANGED
@@ -3,43 +3,36 @@ module Freyr
3
3
  extend Forwardable
4
4
  class << self
5
5
  def add_service_method *methods
6
- def_delegators :@service_info, *methods
6
+
7
7
  end
8
8
  end
9
9
 
10
- attr_reader :service_info, :command
10
+ attr_reader :info, :command, :defined_in_paths
11
+
12
+ def_delegators :info, :name, :groups
11
13
 
12
14
  def initialize(s)
13
- @service_info = s
15
+ @info = s
14
16
  @command = Command.new(self)
15
- end
16
-
17
- def start_command
18
- @service_info.start
19
- end
20
-
21
- def env
22
- service_info.env || {}
23
- end
24
-
25
- def log
26
- @service_info.read_log || @service_info.log || File.join(command.file_dir,"#{name}.log")
27
- end
28
-
29
- def write_log?
30
- @service_info.log && !@service_info.read_log
17
+ Service.by_selector[name] = ServiceGroup.new(self)
18
+ Service.s[name] = self
19
+ Service.by_name[name] = self
20
+ end
21
+ class NoProcMatch < StandardError; end
22
+
23
+ def pid_file
24
+ @pid_file ||= begin
25
+ raise NoProcMatch, "please provide proc_match for service #{@info.name}" unless @info.proc_match
26
+ PidFile.new(@info.pid_file,@info.proc_match)
27
+ end
31
28
  end
32
29
 
33
30
  def start!
34
- if start_command
35
- command.run! unless alive?
36
- else
37
- error("no start_command")
38
- end
31
+ command.run! unless alive?
39
32
  end
40
33
 
41
34
  def stop!
42
- command.kill! if start_command
35
+ command.kill!
43
36
  end
44
37
 
45
38
  def restart!
@@ -47,110 +40,118 @@ module Freyr
47
40
  end
48
41
 
49
42
  def is_group?(name)
50
- groups.find {|g| g.to_s == name.to_s}
43
+ @info.groups.find {|g| g.to_s == name.to_s}
51
44
  end
52
45
 
53
46
  def alive?
54
- command.alive?
47
+ pid_file.alive?
48
+ end
49
+
50
+ def call_graph
51
+ graph = Hash.new {|h,k| h[k]=[]}
52
+ graph[self]
53
+ @info.dependencies.each do |dep|
54
+ if d = Service.s[dep]
55
+ graph[self] << d
56
+ graph.merge!(d.call_graph)
57
+ end
58
+ end
59
+ graph
60
+ end
61
+
62
+ def dependencies(yell = false)
63
+ missing = []
64
+ deps = @info.dependencies.inject([]) do |all,dep|
65
+ if d = Service.s[dep]
66
+ all | (d.dependencies(yell) + [d])
67
+ else
68
+ missing << dep
69
+ all
70
+ end
71
+ end
72
+
73
+ if yell && !missing.empty?
74
+ raise MissingDependency, "missing #{missing.join(', ')} dependencies"
75
+ end
76
+
77
+ deps.compact
55
78
  end
56
79
 
57
80
  def ping!
58
- if ping
81
+ if @info.ping
59
82
  pinger = Pinger.new(self)
60
83
  pinger.ping
61
84
  pinger
62
85
  end
63
86
  end
64
87
 
65
- def read_log
66
- @service_info.log || @service_info.read_log
67
- end
68
-
69
88
  def tail!(size = 600, follow = true)
70
89
  f = follow ? 'f' : ''
71
- if read_log
72
- cmd = "tail -#{size}#{f} #{File.join(dir||'/',read_log)}"
90
+ log_location = @info.log
91
+ if File.exist?(log_location)
92
+ cmd = "tail -#{size}#{f} #{log_location}"
73
93
  Freyr.logger.debug("tailing cmd") {cmd.inspect}
74
94
  exec(cmd)
75
95
  else
76
- error("no logfile found")
96
+ error("no logfile found at #{log_location}")
97
+ abort("no logfile found at #{log_location}")
77
98
  end
78
99
  end
79
100
 
80
101
  def error *args, &blk
81
102
  Freyr.logger.error(*args,&blk)
82
- Freyr.logger.debug("service info for service #{self}") {@service_info.inspect}
103
+ Freyr.logger.debug("service info for service with error #{self}") {@info.inspect}
83
104
  end
84
105
 
85
106
  def describe
86
- %Q{#{name}(#{groups.join(',')}) - #{start_command}}
107
+ %Q{#{@info.name}(#{@info.groups.join(',')}) - #{@info.start}}
87
108
  end
88
109
 
89
110
  def matches?(n)
90
111
  n = n.to_s
91
- return true if name.to_s == n
112
+ return true if @info.name.to_s == n
92
113
 
93
- also.find {|a| a.to_s == n}
114
+ @info.also.find {|a| a.to_s == n}
94
115
  end
95
116
 
96
117
  def inspect
97
- %Q{#<Freyr::Service #{name} #{start_command.inspect}>}
118
+ %Q{#<Freyr::Service #{@info.name} #{@info.start.inspect}>}
98
119
  end
120
+
121
+ class MissingDependency < StandardError; end
99
122
 
100
123
  class << self
101
-
124
+ # Get by name only
102
125
  def s
103
- @all_services ||= []
126
+ @all_services ||= {}
104
127
  end
105
128
 
106
- def names
107
- @all_names ||= []
129
+ def selectors
130
+ by_selector.keys
108
131
  end
109
-
110
- def groups
111
- @all_groups ||= []
132
+
133
+ def by_dir
134
+ @by_dir ||= {}
112
135
  end
113
-
114
- def selectors
115
- names+groups
136
+
137
+ # Get by name and alias
138
+ def by_name
139
+ @by_name ||= {}
116
140
  end
117
-
118
- def add_file f
119
- s
120
-
121
- Freyr.logger.debug('adding file') {f}
122
-
123
- services = ServiceInfo.from_file(f).collect do |ser|
124
- raise 'Cannot have two things of the same name' if selectors.include?(ser.name)
125
- names |= [ser.name]
126
- @all_groups |= ser.groups
127
- Freyr.logger.debug('adding service') {ser.name.inspect}
128
- new(ser)
129
- end
130
-
131
- @all_services += services
141
+
142
+ # by group/alias/name and always a servicegroup
143
+ def by_selector
144
+ @by_selector ||= Hash.new {|h,k| h[k] = ServiceGroup.new}
132
145
  end
133
146
 
134
- def alive?(name)
135
- !!self[name].find do |ser|
136
- ser.alive?
137
- end
147
+ def add_file f
148
+ ServiceInfo.from_file(f)
149
+
150
+ @all_services
138
151
  end
139
152
 
140
153
  def [](name)
141
- group = ServiceGroup.new
142
-
143
- if ser = s.find {|sr| sr.matches?(name)}
144
- group << ser
145
- else
146
- s.each do |sr|
147
- if sr.is_group?(name)
148
- group << sr
149
- end
150
- end
151
- end
152
-
153
- group.empty? ? nil : group
154
+ by_selector.has_key?(name) ? by_selector[name] : nil
154
155
  end
155
156
 
156
157
  end
@@ -1,49 +1,45 @@
1
- require 'delegate'
2
-
3
1
  module Freyr
4
- class ServiceGroup < Array
2
+ class ServiceGroup < DelegateClass(Array)
5
3
  extend Forwardable
4
+ include TSort
6
5
  service_methods = Service.instance_methods - Class.instance_methods
7
6
  def_delegators :first, *service_methods
8
7
 
8
+ def initialize *services
9
+ @all_services = []
10
+ super(services)
11
+ end
12
+
9
13
  def find_by_name(n)
10
14
  find {|s| s.name == n}
11
15
  end
16
+
17
+ def update_services
18
+ dependencies = inject([]) do |deps,svc|
19
+ deps | svc.dependencies(true)
20
+ end
21
+ @all_services = dependencies|@_dc_obj
22
+ end
23
+
24
+ def inspect
25
+ %Q{#<#{self.class.inspect} #{@_dc_obj.collect{|s| s.name}.join(', ')}>}
26
+ end
27
+
28
+ def call_graph
29
+ @call_graph ||= inject(Hash.new {|h,k| h[k]=[]}) do |graph, svc|
30
+ graph.merge(svc.call_graph)
31
+ end
32
+ end
12
33
 
13
- # Take care this can make a stack overflow
14
34
  def run
15
35
  return [] if empty?
16
-
17
- needs_to_run = ServiceGroup.new
18
-
19
- kill = false
20
- names = []
21
-
22
- each do |svc|
23
-
24
- unless svc.dependencies.empty?
25
- if n = svc.dependencies.find {|s| !Service.alive?(s)}
26
- if find_by_name(n)
27
- needs_to_run << svc
28
- elsif s = Service[n].first
29
- needs_to_run << s
30
- needs_to_run << svc
31
- else
32
- puts "Can't run #{svc.name} because dependency #{n} cannot be found"
33
- kill = true
34
- end
35
-
36
- next
37
- end
38
- end
39
-
40
- Freyr.logger.debug('starting service') {svc.name}
41
- pid = svc.start!
42
- names << svc.name if pid
36
+
37
+ @call_graph = nil # Make sure it's getting the latest graph
38
+ services = call_order
39
+
40
+ services.collect do |service|
41
+ service.name if service.start!
43
42
  end
44
-
45
- names += needs_to_run.run unless kill
46
- names
47
43
  end
48
44
 
49
45
  def stop
@@ -67,6 +63,15 @@ module Freyr
67
63
 
68
64
  names
69
65
  end
70
-
66
+
67
+ def tsort_each_node &blk
68
+ call_graph.keys.each(&blk)
69
+ end
70
+
71
+ def tsort_each_child(node, &blk)
72
+ call_graph[node].each(&blk)
73
+ end
74
+ alias call_order tsort
75
+
71
76
  end
72
77
  end
@@ -1,41 +1,153 @@
1
1
  module Freyr
2
2
  class ServiceInfo
3
3
  attr_reader :groups
4
-
5
- class << self
6
- def add_service_method *methods
7
- Service.send :add_service_method, *methods
8
- Command.send :add_service_method, *methods
9
- methods.each do |method|
10
- ATTRS << methods
4
+ ROOT_PIDS = '/var/run/freyr'.freeze
5
+ USER_PIDS = File.expand_path('.freyr', '~').freeze
6
+ ROOT_LOGS = '/var/log/freyr'.freeze
7
+ USER_LOGS = USER_PIDS
8
+ failed = []
9
+ [ROOT_PIDS,USER_PIDS,ROOT_LOGS,USER_LOGS].each do |dir|
10
+ begin
11
+ FileUtils.mkdir_p(dir)
12
+ rescue Errno::EACCES => e
13
+ failed << dir
14
+ end
15
+ end
16
+
17
+ module Base
18
+
19
+ module ClassMethods
20
+
21
+ def add_service_method *methods
22
+ Service.send :add_service_method, *methods
23
+ Command.send :add_service_method, *methods
24
+ methods.each do |method|
25
+ InstanceMethods.instance_eval do
26
+ define_method method do |*args|
27
+ val = args
28
+ val = val.first if val.size < 2
29
+ if val
30
+ MODIFIERS[method].each do |mod|
31
+ val = send(mod,val)
32
+ end
33
+ instance_variable_set("@#{method}",val)
34
+ else
35
+ instance_variable_get("@#{method}")
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
11
42
  end
43
+
44
+ end
45
+
46
+ module InstanceMethods
47
+
48
+ end
49
+
50
+ def self.included(receiver)
51
+ receiver.extend ClassMethods
52
+ receiver.send :include, InstanceMethods
12
53
  end
13
54
  end
55
+ include Base
56
+
14
57
 
58
+ MODIFIERS = Hash.new {|h,k| h[k] = []}
59
+ MODIFIERS[:start] << :_sudo_checker
60
+ MODIFIERS[:stop] << :_sudo_checker
61
+ MODIFIERS[:restart] << :_sudo_checker
15
62
  ATTRS = []
16
- add_service_method :name,:dir,:log_cmd,:log,:err_log_cmd,:err_log,:umask,
63
+ attr_reader :defined_in
64
+ add_service_method :start,:name,:dir,:log_cmd,:log,:err_log_cmd,:err_log,:umask,
17
65
  :uid,:gid,:chroot,:proc_match,:restart,:stop,:stop_sig,
18
- :restart_sig,:sudo,:groups,:ping,:also,:dependencies,:read_log
19
-
66
+ :restart_sig,:sudo,:groups,:ping,:also,:dependencies,:read_log,
67
+ :pid_file, :dont_write_log,:env, :rvm
68
+ alias log_file log
20
69
  def initialize(name=nil, args={}, &block)
21
70
  @groups = []
22
71
  @also = []
23
72
  @dependencies = []
73
+ @defined_in = []
24
74
  if name.is_a?(Hash)
25
75
  @name = name.keys.first
26
76
  @groups << name[@name]
27
77
  else
28
78
  @name = name
29
79
  end
80
+
81
+ @service = Service.new(self)
30
82
 
31
- instance_eval(&block)
83
+ instance_eval(&block) if block_given?
32
84
  end
33
85
 
34
86
  def use_sudo
35
87
  @sudo = true
36
88
  end
89
+
90
+ def env val=nil
91
+ raise TypeError, 'environment must be a hash' unless val.is_a?(Hash) || val.nil?
92
+ if val = super
93
+ val
94
+ else
95
+ {}
96
+ end
97
+ end
37
98
 
99
+ def pid_file val=nil
100
+ if val = super
101
+ val
102
+ else
103
+ if @sudo
104
+ File.join(ROOT_PIDS,"#{@name}.pid")
105
+ else
106
+ File.join(USER_PIDS,"#{@name}.pid")
107
+ end
108
+ end
109
+ end
110
+
111
+ def read_log val=nil
112
+ @dont_write_log = true
113
+ super
114
+ end
115
+
116
+ def log val=nil
117
+ val = if val = super
118
+ val
119
+ else
120
+ if @read_log
121
+ @read_log
122
+ else
123
+ if @sudo
124
+ File.join(ROOT_LOGS,"#{@name}.log")
125
+ else
126
+ File.join(USER_LOGS,"#{@name}.log")
127
+ end
128
+ end
129
+ end
130
+
131
+ val =~ /^\// ? val : File.join(dir,val)
132
+ end
133
+
134
+ def dir val=nil
135
+ if val
136
+ val.sub! /(.)\/$/, '\1' # remove tailing slash
137
+ Service.by_dir[val] = @service
138
+ end
139
+
140
+ if val = super
141
+ val
142
+ else
143
+ '/'
144
+ end
145
+ end
146
+
38
147
  def group(*val)
148
+ val.each do |group|
149
+ Service.by_selector[group] << @service
150
+ end
39
151
  @groups |= val
40
152
  end
41
153
 
@@ -44,25 +156,11 @@ module Freyr
44
156
  end
45
157
 
46
158
  def also_as(*val)
47
- @also |= val
48
- end
49
-
50
- MODIFIERS = Hash.new {|h,k| h[k] = []}
51
- MODIFIERS[:start] << :_sudo_checker
52
- MODIFIERS[:stop] << :_sudo_checker
53
- MODIFIERS[:restart] << :_sudo_checker
54
-
55
- def method_missing key, val=nil
56
- key = key.to_s.gsub(/\=$/,'').to_sym
57
-
58
- if val
59
- MODIFIERS[key].each do |modifier|
60
- val = send(modifier,val)
61
- end
62
- instance_variable_set("@#{key}", val)
63
- else
64
- instance_variable_get("@#{key}")
159
+ val.each do |aliaz|
160
+ Service.by_selector[aliaz] = Service.by_selector[name]
161
+ Service.by_name[aliaz] = Service.by_name[name]
65
162
  end
163
+ @also |= val
66
164
  end
67
165
 
68
166
  SUDO_MATCHER = /^sudo\s+/
@@ -85,13 +183,15 @@ module Freyr
85
183
  def from_file file
86
184
  @file_path = file
87
185
  file = File.expand_path(file)
186
+ Freyr.logger.debug("adding file") {file}
88
187
  return [] unless File.exist?(file)
89
188
  @added_services = []
90
- instance_eval(File.open(file).read)
189
+ @namespace = nil
190
+ instance_eval(File.open(file).read,file,0)
91
191
  @added_services
92
192
  end
93
193
 
94
- def method_missing name, *args
194
+ def method_missing name, *args, &blk
95
195
  STDERR.puts "Freyr doesn't support #{name} as used in #{@file_path}"
96
196
  end
97
197
 
@@ -103,24 +203,22 @@ module Freyr
103
203
 
104
204
  def group name, *services
105
205
  services.each do |s|
106
- services = Service[s]
107
- if services
108
- services.each do |service|
109
- service.service_info.group(name)
110
- end
111
- else
112
- STDERR.puts "Service #{s} not found, can't add to group #{name} as attempted in #{@file_path}"
206
+ service s do
207
+ group name
113
208
  end
114
209
  end
115
210
  end
116
211
 
117
212
  def service name=nil, &blk
118
213
  name = "#{@namespace}:#{name}" if @namespace
119
- if service = Service[name]
120
- service.service_info.instance_eval(&blk)
214
+ if service = Service.s[name]
215
+ service_info = service.info
121
216
  else
122
- @added_services << new(name,&blk)
217
+ service_info = new(name)
123
218
  end
219
+ @added_services << service_info
220
+ service_info.defined_in << @file_path
221
+ service_info.instance_eval(&blk)
124
222
  end
125
223
  end
126
224
 
data/lib/freyr.rb CHANGED
@@ -1,16 +1,33 @@
1
+ require 'delegate'
1
2
  require 'forwardable'
2
3
  require 'logger'
4
+ require 'fileutils'
5
+ require 'tsort'
3
6
 
4
7
  module Freyr
5
- def self.logger
6
- @logger ||= Logger.new("/dev/null")
8
+ extend self
9
+ OUT = STDOUT.dup
10
+
11
+ def logger
12
+ @logger ||= begin
13
+ log = Logger.new(STDOUT)
14
+ log.level = Logger::FATAL
15
+ log.formatter = proc do |severity, datetime, progname, msg|
16
+ %Q{#{severity.chars.first}: #{[progname,msg].compact.join(' - ')}\n}
17
+ end
18
+ log
19
+ end
7
20
  end
8
21
 
9
- def self.logger= logger
22
+ def logger= logger
10
23
  @logger = logger
11
24
  end
12
25
  end
13
26
 
14
- %w{version service service_group command service_info pinger process_info}.each do |f|
27
+ if ARGV.include?('--trace')
28
+ Freyr.logger.level = Logger::DEBUG
29
+ end
30
+
31
+ %w{version helpers service service_group command service_info pid_file pinger process_info rvm}.each do |f|
15
32
  require File.expand_path(File.dirname(__FILE__)+"/freyr/#{f}.rb")
16
33
  end
data/spec/freyr_spec.rb CHANGED
@@ -1,7 +1,4 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- describe "Freyr" do
4
- it "fails" do
5
- fail "hey buddy, you should probably rename this file and start specing for real"
6
- end
3
+ describe Freyr do
7
4
  end