vuderacha-syrup 0.1.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.
- data/README.rdoc +57 -0
- data/Rakefile +69 -0
- data/VERSION.yml +4 -0
- data/bin/syrup +10 -0
- data/lib/launcher.rb +10 -0
- data/lib/syrup/config_store.rb +169 -0
- data/lib/syrup/daemon.rb +58 -0
- data/lib/syrup/executor.rb +20 -0
- data/lib/syrup/fabric_support.rb +31 -0
- data/lib/syrup/fabrics/default.rb +2 -0
- data/lib/syrup/fabrics/rack.rb +4 -0
- data/lib/syrup/manager.rb +253 -0
- data/lib/syrup/runner.rb +71 -0
- data/lib/syrup.rb +211 -0
- data/spec/syrup/config_store_spec.rb +190 -0
- data/spec/syrup/runner_spec.rb +244 -0
- data/spec/syrup_spec.rb +8 -0
- metadata +73 -0
data/README.rdoc
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
= Syrup - Service Management
|
2
|
+
Syrup provides mechanism for managing your application daemons. Instead of needing
|
3
|
+
to manually background processes (such as queue workers), you just configure
|
4
|
+
a config.sy file, and then start and stop it.
|
5
|
+
|
6
|
+
== Getting Started
|
7
|
+
Syrup assumes that you'll deploy one application group per user account. You can
|
8
|
+
run more if you want to, but Syrup makes it a lot easier if you don't...
|
9
|
+
|
10
|
+
Firstly, download and install syrup
|
11
|
+
git clone git://github.com/vuderacha/syrup.git
|
12
|
+
cd syrup
|
13
|
+
rake install
|
14
|
+
|
15
|
+
Assuming you have a simple application such as:
|
16
|
+
# test_service.rb
|
17
|
+
while true
|
18
|
+
File.open('/tmp/test.txt', 'a') {|f| f << "a\n"}
|
19
|
+
sleep 2
|
20
|
+
|
21
|
+
Create a configuration file for Syrup to run it:
|
22
|
+
# config.sy
|
23
|
+
service 'myservice', 'test-service.rb'
|
24
|
+
|
25
|
+
For Syrup to use an application, you need to "activate" it. This informs Syrup about the
|
26
|
+
path of the application, and allows it to automatically start the app again later when you
|
27
|
+
may not be around to tell it about it! Assuming that you've put your app into /home/syruptest/app,
|
28
|
+
then you'll just issue the command:
|
29
|
+
syrup activate /home/syruptest/app
|
30
|
+
|
31
|
+
This will create a file called "activated" in /home/syruptest/.syrup containing the path to this app.
|
32
|
+
|
33
|
+
Finally, start the application with:
|
34
|
+
syrup start
|
35
|
+
|
36
|
+
Conversely, stop it with:
|
37
|
+
syrup stop
|
38
|
+
|
39
|
+
== Supported Application Types
|
40
|
+
Syrup currently supports two types of application. Services are declared in the form
|
41
|
+
service "<name>", "<file>"
|
42
|
+
|
43
|
+
Rack applications are also supported. To run a rack application, add the following to your config.sy:
|
44
|
+
rack "<name>", "<rack arguments>"
|
45
|
+
Note that if your config.ru doesn't require any arguments, then the second parameter is entirely optional.
|
46
|
+
|
47
|
+
== Persistent Configuration
|
48
|
+
Often, applications need per deployment configuration. One example is configuring whether a given host is
|
49
|
+
production or development. Whilst there are many ways to do this, Syrup adds yet another. Within a given
|
50
|
+
account, commands like the following can be issued:
|
51
|
+
syrup set PROP=VALUE
|
52
|
+
This property will be permanently stored (in ~/.syrup/props), and will be applied into the environment of
|
53
|
+
all applications loaded through Syrup at their next start. So, for example, if your app had a line that read
|
54
|
+
puts RACK_ENV
|
55
|
+
then executing
|
56
|
+
syrup set RACK_ENV=production
|
57
|
+
would result in the application reporting "production" upon reaching the aforementioned line.
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc "Run all specs"
|
5
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
6
|
+
t.spec_opts = ["-cfs"]
|
7
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
8
|
+
t.libs = ['lib']
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Print specdocs"
|
12
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
13
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
14
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Generate RCov code coverage report"
|
18
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
19
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
20
|
+
t.rcov = true
|
21
|
+
t.rcov_opts = ['--exclude', 'examples']
|
22
|
+
end
|
23
|
+
|
24
|
+
task :default => :spec
|
25
|
+
|
26
|
+
######################################################
|
27
|
+
|
28
|
+
require 'rake'
|
29
|
+
require 'rake/testtask'
|
30
|
+
require 'rake/clean'
|
31
|
+
require 'rake/gempackagetask'
|
32
|
+
require 'rake/rdoctask'
|
33
|
+
require 'fileutils'
|
34
|
+
include FileUtils
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'jeweler'
|
38
|
+
Jeweler::Tasks.new do |s|
|
39
|
+
s.name = "syrup"
|
40
|
+
s.summary = "Ruby service application manager."
|
41
|
+
s.description = "Syrup is a process manager for working with services. It provides the ability to deploy and manage long-running services."
|
42
|
+
s.author = "Paul Jones"
|
43
|
+
s.email = "pauljones23@gmail.com"
|
44
|
+
s.homepage = "http://github.com/vuderacha/syrup/"
|
45
|
+
s.executables = [ "syrup" ]
|
46
|
+
s.default_executable = "syrup"
|
47
|
+
|
48
|
+
s.files = %w(Rakefile README.rdoc VERSION.yml) + Dir.glob("{bin,lib,spec}/**/*")
|
49
|
+
|
50
|
+
s.require_path = "lib"
|
51
|
+
s.bindir = "bin"
|
52
|
+
end
|
53
|
+
rescue LoadError
|
54
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
55
|
+
end
|
56
|
+
|
57
|
+
task :test => [ :spec ]
|
58
|
+
|
59
|
+
Rake::RDocTask.new do |t|
|
60
|
+
t.rdoc_dir = 'rdoc'
|
61
|
+
t.title = "Syrup -- Process Manager"
|
62
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
63
|
+
t.options << '--charset' << 'utf-8'
|
64
|
+
t.rdoc_files.include('README.rdoc')
|
65
|
+
t.rdoc_files.include('lib/syrup/*.rb')
|
66
|
+
end
|
67
|
+
|
68
|
+
CLEAN.include [ 'build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log', 'pkg', 'lib/*.bundle', '*.gem', '.config' ]
|
69
|
+
|
data/VERSION.yml
ADDED
data/bin/syrup
ADDED
data/lib/launcher.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
module Syrup
|
2
|
+
# Module that allows a given object to store a Fabric. Requires the including module to have
|
3
|
+
# a fabric_fn method.
|
4
|
+
module WithFabric
|
5
|
+
def fabric
|
6
|
+
return File.read(fabric_fn) if File.file? fabric_fn
|
7
|
+
return nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def fabric=(path)
|
11
|
+
unless path.nil?
|
12
|
+
File.open(fabric_fn, 'w'){ |f| f.write(File.expand_path(path)) }
|
13
|
+
else
|
14
|
+
File.delete fabric_fn if File.file? fabric_fn
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ConfigStore
|
20
|
+
include WithFabric
|
21
|
+
|
22
|
+
attr_reader :properties
|
23
|
+
|
24
|
+
# Initializes the configuration store with the given storage directory.
|
25
|
+
def initialize(path)
|
26
|
+
@path = path
|
27
|
+
@properties = StoredHash.new File.join(@path, 'props')
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieves all of the applications that are configured within the system
|
31
|
+
def applications
|
32
|
+
apps = {}
|
33
|
+
Dir[File.join(@path, '*')].each do |store_dir|
|
34
|
+
name = File.basename(store_dir)
|
35
|
+
|
36
|
+
apps[name] = ApplicationConfiguration.new name, store_dir
|
37
|
+
end
|
38
|
+
|
39
|
+
apps
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a new application
|
43
|
+
def create_application(name)
|
44
|
+
store_dir = File.join(@path, name)
|
45
|
+
FileUtils.mkdir_p store_dir
|
46
|
+
|
47
|
+
ApplicationConfiguration.new name, store_dir
|
48
|
+
end
|
49
|
+
|
50
|
+
def fabric_fn
|
51
|
+
File.join(@path, 'fabric')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class ApplicationConfiguration
|
56
|
+
include WithFabric
|
57
|
+
|
58
|
+
attr_reader :name
|
59
|
+
attr_reader :properties
|
60
|
+
|
61
|
+
def initialize(name, path)
|
62
|
+
@name = name
|
63
|
+
@path = path
|
64
|
+
@properties = StoredHash.new File.join(@path, 'props')
|
65
|
+
@start_parameters = if File.file? start_params_fn then YAML::load_file(start_params_fn) else [] end
|
66
|
+
end
|
67
|
+
|
68
|
+
def app
|
69
|
+
return File.read(activated_fn) if File.file? activated_fn
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def app=(path)
|
74
|
+
unless path.nil?
|
75
|
+
File.open(activated_fn, 'w'){ |f| f.write(File.expand_path(path)) }
|
76
|
+
else
|
77
|
+
File.delete activated_fn if File.file? activated_fn
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def pid
|
82
|
+
result = File.read(pid_fn) rescue nil
|
83
|
+
return result.to_i unless result.nil?
|
84
|
+
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def pid=(i)
|
89
|
+
unless i.nil?
|
90
|
+
File.open(pid_fn, 'w'){ |f| f.write(i) }
|
91
|
+
else
|
92
|
+
File.delete pid_fn if File.file? pid_fn
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def start_parameters
|
97
|
+
@start_parameters
|
98
|
+
end
|
99
|
+
|
100
|
+
def start_parameters=(params)
|
101
|
+
@start_parameters = params
|
102
|
+
|
103
|
+
unless params.nil? or params.length == 0
|
104
|
+
File.open(start_params_fn, 'w') { |f| f.write(params.to_yaml) }
|
105
|
+
else
|
106
|
+
File.delete start_params_fn if File.file? start_params_fn
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def activated_fn
|
111
|
+
File.join(@path, 'activated')
|
112
|
+
end
|
113
|
+
|
114
|
+
def props_fn
|
115
|
+
File.join(@path, 'props')
|
116
|
+
end
|
117
|
+
|
118
|
+
def fabric_fn
|
119
|
+
File.join(@path, 'fabric')
|
120
|
+
end
|
121
|
+
|
122
|
+
def start_params_fn
|
123
|
+
File.join(@path, 'start_params')
|
124
|
+
end
|
125
|
+
|
126
|
+
def pid_fn
|
127
|
+
File.join(@path, 'pid')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class StoredHash
|
132
|
+
def initialize(file)
|
133
|
+
@file = file
|
134
|
+
@props = if File.file? file then YAML::load_file(file) else {} end
|
135
|
+
end
|
136
|
+
|
137
|
+
def [](k)
|
138
|
+
@props[k]
|
139
|
+
end
|
140
|
+
|
141
|
+
def []=(k, v)
|
142
|
+
@props[k] = v
|
143
|
+
|
144
|
+
save!
|
145
|
+
end
|
146
|
+
|
147
|
+
def length
|
148
|
+
@props.length
|
149
|
+
end
|
150
|
+
|
151
|
+
def clear
|
152
|
+
@props.clear
|
153
|
+
File.delete @file if File.file? @file
|
154
|
+
end
|
155
|
+
|
156
|
+
def delete(key)
|
157
|
+
@props.delete(key)
|
158
|
+
save!
|
159
|
+
end
|
160
|
+
|
161
|
+
def each(&block)
|
162
|
+
@props.each(&block)
|
163
|
+
end
|
164
|
+
|
165
|
+
def save!
|
166
|
+
File.open(@file, 'w') {|f| f << @props.to_yaml}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/lib/syrup/daemon.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Syrup
|
4
|
+
class Daemon
|
5
|
+
def initialize(config_directory, app)
|
6
|
+
@config_directory = config_directory
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
# Check if the process is already running first
|
12
|
+
if File.file? pid_fn
|
13
|
+
running = (not Process.getpgid(recall_pid).nil?) rescue false
|
14
|
+
if running
|
15
|
+
puts "Syrup is already running. Stop Syrup before trying to start it"
|
16
|
+
exit
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
fork do
|
21
|
+
Process.setsid
|
22
|
+
exit if fork
|
23
|
+
store_pid(Process.pid)
|
24
|
+
#Dir.chdir WorkingDirectory
|
25
|
+
File.umask 0000
|
26
|
+
STDIN.reopen "/dev/null"
|
27
|
+
STDOUT.reopen "/dev/null", "a"
|
28
|
+
STDERR.reopen STDOUT
|
29
|
+
trap("TERM") {@app.stop; exit}
|
30
|
+
@app.start
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop
|
35
|
+
if !File.file?(pid_fn)
|
36
|
+
puts "Pid file not found. Is the daemon started?"
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
pid = recall_pid
|
40
|
+
FileUtils.rm(pid_fn)
|
41
|
+
pid && Process.kill("TERM", pid)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def pid_fn
|
46
|
+
File.join(File.expand_path(@config_directory), 'syrup.pid')
|
47
|
+
end
|
48
|
+
|
49
|
+
def store_pid(pid)
|
50
|
+
FileUtils.mkdir_p File.dirname(pid_fn)
|
51
|
+
File.open(pid_fn, 'w') {|f| f << pid}
|
52
|
+
end
|
53
|
+
|
54
|
+
def recall_pid
|
55
|
+
IO.read(pid_fn).to_i rescue nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Syrup
|
2
|
+
class Executor
|
3
|
+
# Executes a script in the "foreground" (ie, it only forks once). The pid is recorded
|
4
|
+
# in memory, and the process will return.
|
5
|
+
def execute_foreground(name, script, fabric, props)
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
# Kills all processes running in the foreground.
|
10
|
+
def kill_all_foreground
|
11
|
+
end
|
12
|
+
|
13
|
+
# Starts a script as a daemon. Returns the pid.
|
14
|
+
def execute_daemon(name, script, fabric, props)
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop_daemon(name, pid)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Support functions added to the kernel for Fabrics to work with
|
2
|
+
module Kernel
|
3
|
+
# Indicates that a Fabric feature with the given name is required for the application to
|
4
|
+
# proceed. If the feature is initialized on demand, then it is expected that this feature
|
5
|
+
# will be initialized before this method returns.
|
6
|
+
def fabric_requirement(name)
|
7
|
+
Syrup.fabric_support.require_feature(name)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Syrup
|
12
|
+
class FabricSupport
|
13
|
+
def initialize
|
14
|
+
@features = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def require_feature(name)
|
18
|
+
raise "Feature #{name} was not provided by the Fabric!" unless @features.include? name
|
19
|
+
end
|
20
|
+
|
21
|
+
def register_feature(name)
|
22
|
+
@features << name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class <<self
|
27
|
+
def fabric_support
|
28
|
+
@fabric_support ||= Syrup::FabricSupport.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'syrup/config_store'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Syrup
|
5
|
+
# Manager for controlling the Syrup functionality
|
6
|
+
class Manager
|
7
|
+
def initialize(config_dir, logger, verbose)
|
8
|
+
@config_dir = config_dir
|
9
|
+
@logger = logger
|
10
|
+
@verbose = verbose
|
11
|
+
|
12
|
+
@config_store = Syrup::ConfigStore.new @config_dir
|
13
|
+
@runner = Syrup::Runner.new @config_store, @logger
|
14
|
+
end
|
15
|
+
|
16
|
+
# Informs the manager to start all configured applications
|
17
|
+
def start_all
|
18
|
+
if @config_store.applications.length == 0
|
19
|
+
@logger.warn 'No Applications activated yet. Nothing to do!'
|
20
|
+
return true
|
21
|
+
end
|
22
|
+
|
23
|
+
@config_store.applications.each do |app_name, app|
|
24
|
+
start(app_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Informs the manager to start the specified application
|
29
|
+
def start(app_name)
|
30
|
+
# Retrieve the details of the application to check that it is valid
|
31
|
+
app = get_application(app_name, false)
|
32
|
+
return false if app.nil?
|
33
|
+
|
34
|
+
fork do
|
35
|
+
Process.setsid
|
36
|
+
exit if fork
|
37
|
+
|
38
|
+
# Record our PID
|
39
|
+
app.pid = Process.pid
|
40
|
+
|
41
|
+
#Dir.chdir @working_dir
|
42
|
+
File.umask 0000
|
43
|
+
STDIN.reopen "/dev/null"
|
44
|
+
STDOUT.reopen "log/#{app_name}.txt", "a"
|
45
|
+
STDERR.reopen STDOUT
|
46
|
+
trap("TERM") {exit}
|
47
|
+
|
48
|
+
at_exit do
|
49
|
+
# Release our pid
|
50
|
+
app.pid = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# Run the application
|
54
|
+
run_app(app_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Informs the manager to stop all configured applications
|
61
|
+
def stop_all
|
62
|
+
if @config_store.applications.length == 0
|
63
|
+
@logger.warn 'No Applications activated yet. Nothing to do!'
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
|
67
|
+
stop(@config_store.applications.keys)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Informs the manager to stop the named applications
|
71
|
+
def stop(app_names)
|
72
|
+
pids = []
|
73
|
+
@config_store.applications.each do |app_name, app|
|
74
|
+
pids << app.pid if app_names.include? app_name and not app.pid.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
kill_pids pids
|
78
|
+
end
|
79
|
+
|
80
|
+
# Informs the manager to activate the given path
|
81
|
+
def activate name, path, args
|
82
|
+
# Retrieve ourselves an application object
|
83
|
+
app = @config_store.applications[name]
|
84
|
+
app = @config_store.create_application(name) if app.nil?
|
85
|
+
|
86
|
+
# Store the path to the application
|
87
|
+
app.app = File.expand_path(path)
|
88
|
+
app.start_parameters = args
|
89
|
+
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
# Informs the manager to run the given application in the foreground
|
94
|
+
def run(names)
|
95
|
+
# Create sub-processes for each of the named applications
|
96
|
+
pids = []
|
97
|
+
names.each do |name|
|
98
|
+
pids << fork do
|
99
|
+
run_app(name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Register an at_exit handler to kill off all the pids
|
104
|
+
at_exit do
|
105
|
+
kill_pids pids
|
106
|
+
end
|
107
|
+
|
108
|
+
# Wait for all children to die
|
109
|
+
Process.waitall.each do |pid, status|
|
110
|
+
# We won't need to kill the processes that successfully exited
|
111
|
+
pids.delete pid if status.exited?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Informs the manager to run the given application within the current process
|
116
|
+
def run_app(name)
|
117
|
+
@runner.run(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Requests that the manager store the given variables as persistent configuration for the given application.
|
121
|
+
def set_app_properties(app_name, properties)
|
122
|
+
app = @config_store.applications[app_name]
|
123
|
+
if app.nil?
|
124
|
+
@logger.error "Unknown Application. Cannot set properties."
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
|
128
|
+
props.each do |pair|
|
129
|
+
k,v = pair.split('=')
|
130
|
+
if k.nil? or v.nil?
|
131
|
+
@logger.error "Invalid set command. #{pair} not in the form K=V"
|
132
|
+
return false
|
133
|
+
end
|
134
|
+
|
135
|
+
app.properties[k] = v
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Requests that the manager store the given variables as persistent configuration that will be
|
140
|
+
# restored when all applications are started
|
141
|
+
def set_global_properties(props)
|
142
|
+
props.each do |pair|
|
143
|
+
k,v = pair.split('=')
|
144
|
+
if k.nil? or v.nil?
|
145
|
+
@logger.error "Invalid set command. #{pair} not in the form K=V"
|
146
|
+
return false
|
147
|
+
end
|
148
|
+
|
149
|
+
@config_store.properties[k] = v
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Requests that the manager remove the given keys from the stored properties for the given app.
|
154
|
+
def unset_app_properties(app_name, props)
|
155
|
+
app = @config_store.applications[app_name]
|
156
|
+
if app.nil?
|
157
|
+
@logger.error "Unknown Application. Cannot unset properties."
|
158
|
+
return false
|
159
|
+
end
|
160
|
+
|
161
|
+
props.each do |prop|
|
162
|
+
app.properties.delete prop
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Requests that the manager remove the given keys from the stored properties
|
167
|
+
def unset_global_properties(props)
|
168
|
+
props.each do |prop|
|
169
|
+
@config_store.properties.delete prop
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Removes all stored properties for the current profile
|
174
|
+
def clear_app_properties
|
175
|
+
app = @config_store.applications[app_name]
|
176
|
+
return true if app.nil?
|
177
|
+
|
178
|
+
app.properties.clear
|
179
|
+
end
|
180
|
+
|
181
|
+
# Removes all stored properties for the current profile
|
182
|
+
def clear_global_properties
|
183
|
+
@config_store.properties.clear
|
184
|
+
end
|
185
|
+
|
186
|
+
# Requests that the manager load the given fabric for applications within the current profile
|
187
|
+
def weave_global(fabric)
|
188
|
+
@config_store.fabric = fabric
|
189
|
+
end
|
190
|
+
|
191
|
+
def unweave_global
|
192
|
+
@config_store.fabric = nil
|
193
|
+
end
|
194
|
+
|
195
|
+
def weave_for_application(app_name, fabric)
|
196
|
+
app = get_application(app_name, false)
|
197
|
+
return false if app.nil?
|
198
|
+
|
199
|
+
app.fabric = fabric
|
200
|
+
end
|
201
|
+
|
202
|
+
def unweave_for_application(app_name)
|
203
|
+
app = get_application(app_name, false)
|
204
|
+
return false if app.nil?
|
205
|
+
|
206
|
+
app.fabric = nil
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
def get_application(app_name, create_if_missing)
|
211
|
+
app = @config_store.applications[app_name]
|
212
|
+
app = @config_store.create_application(app_name) if create_if_missing and app.nil?
|
213
|
+
|
214
|
+
if app.nil?
|
215
|
+
@logger.error "Unknown Application #{app_name}"
|
216
|
+
end
|
217
|
+
|
218
|
+
app
|
219
|
+
end
|
220
|
+
|
221
|
+
def kill_pids(pids)
|
222
|
+
# List of pids waiting at each round
|
223
|
+
waiting_pids = []
|
224
|
+
|
225
|
+
(1..5).each do
|
226
|
+
waiting_pids = []
|
227
|
+
pids.each do |pid|
|
228
|
+
pid && Process.kill("TERM", pid) && waiting_pids << pid rescue puts "WARNING: Failed to kill #{pid}"
|
229
|
+
end
|
230
|
+
|
231
|
+
# Wait for the process to die
|
232
|
+
(1..20).each do
|
233
|
+
waiting_pids.each do |pid|
|
234
|
+
running = (not Process.getpgid(pid).nil?) rescue false
|
235
|
+
waiting_pids.delete pid unless running
|
236
|
+
end
|
237
|
+
|
238
|
+
break if waiting_pids.empty?
|
239
|
+
STDERR.write "."
|
240
|
+
sleep 0.5
|
241
|
+
end
|
242
|
+
|
243
|
+
break if waiting_pids.empty?
|
244
|
+
pids.replace waiting_pids
|
245
|
+
end
|
246
|
+
|
247
|
+
# Write a newline to clear the '.'s
|
248
|
+
STDERR.write "\n"
|
249
|
+
|
250
|
+
@logger.warn "Process(es) #{waiting_pids.inspect} did not terminate" unless waiting_pids.empty?
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|