freyr 0.4.2 → 0.5.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.
@@ -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