vuderacha-syrup 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|