vcap_services_base 0.2.10
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.
- checksums.yaml +7 -0
- data/lib/base/abstract.rb +11 -0
- data/lib/base/api/message.rb +31 -0
- data/lib/base/asynchronous_service_gateway.rb +529 -0
- data/lib/base/backup.rb +206 -0
- data/lib/base/barrier.rb +54 -0
- data/lib/base/base.rb +159 -0
- data/lib/base/base_async_gateway.rb +164 -0
- data/lib/base/base_job.rb +5 -0
- data/lib/base/catalog_manager_base.rb +67 -0
- data/lib/base/catalog_manager_v1.rb +225 -0
- data/lib/base/catalog_manager_v2.rb +291 -0
- data/lib/base/cloud_controller_services.rb +75 -0
- data/lib/base/datamapper_l.rb +148 -0
- data/lib/base/gateway.rb +167 -0
- data/lib/base/gateway_service_catalog.rb +68 -0
- data/lib/base/http_handler.rb +101 -0
- data/lib/base/job/async_job.rb +71 -0
- data/lib/base/job/config.rb +27 -0
- data/lib/base/job/lock.rb +153 -0
- data/lib/base/job/package.rb +112 -0
- data/lib/base/job/serialization.rb +365 -0
- data/lib/base/job/snapshot.rb +354 -0
- data/lib/base/node.rb +471 -0
- data/lib/base/node_bin.rb +154 -0
- data/lib/base/plan.rb +63 -0
- data/lib/base/provisioner.rb +1120 -0
- data/lib/base/provisioner_v1.rb +125 -0
- data/lib/base/provisioner_v2.rb +193 -0
- data/lib/base/service.rb +93 -0
- data/lib/base/service_advertiser.rb +184 -0
- data/lib/base/service_error.rb +122 -0
- data/lib/base/service_message.rb +94 -0
- data/lib/base/service_plan_change_set.rb +11 -0
- data/lib/base/simple_aop.rb +63 -0
- data/lib/base/snapshot_v2/snapshot.rb +227 -0
- data/lib/base/snapshot_v2/snapshot_client.rb +158 -0
- data/lib/base/snapshot_v2/snapshot_job.rb +95 -0
- data/lib/base/utils.rb +63 -0
- data/lib/base/version.rb +7 -0
- data/lib/base/warden/instance_utils.rb +161 -0
- data/lib/base/warden/node_utils.rb +205 -0
- data/lib/base/warden/service.rb +426 -0
- data/lib/base/worker_bin.rb +76 -0
- data/lib/vcap_services_base.rb +16 -0
- metadata +364 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
module VCAP
|
2
|
+
module Services
|
3
|
+
module Base
|
4
|
+
module Warden
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module VCAP::Services::Base::Warden::NodeUtils
|
11
|
+
|
12
|
+
attr_accessor :m_interval, :m_actions
|
13
|
+
|
14
|
+
def warden_node_init(options={})
|
15
|
+
@m_interval = options[:m_interval] || 10
|
16
|
+
@m_actions = options[:m_actions] || []
|
17
|
+
setup_monitor_timer
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup_monitor_timer
|
21
|
+
EM.add_timer(m_interval){ EM.defer{ monitor_all_instances } } if m_interval > 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def service_instances
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
|
28
|
+
def init_ports(port_range)
|
29
|
+
@free_ports = Set.new
|
30
|
+
port_range.each {|port| @free_ports << port}
|
31
|
+
@free_ports_lock = Mutex.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def new_port(port=nil)
|
35
|
+
@free_ports_lock.synchronize do
|
36
|
+
raise "No ports available." if @free_ports.empty?
|
37
|
+
if port.nil? || !@free_ports.include?(port)
|
38
|
+
port = @free_ports.first
|
39
|
+
@free_ports.delete(port)
|
40
|
+
else
|
41
|
+
@free_ports.delete(port)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
port
|
45
|
+
end
|
46
|
+
|
47
|
+
def free_port(port)
|
48
|
+
@free_ports_lock.synchronize do
|
49
|
+
raise "port #{port} already freed!" if @free_ports.include?(port)
|
50
|
+
@free_ports.add(port)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def del_port(port)
|
55
|
+
@free_ports_lock.synchronize do
|
56
|
+
@free_ports.delete(port)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def port_occupied?(port)
|
61
|
+
Timeout::timeout(1) do
|
62
|
+
begin
|
63
|
+
TCPSocket.open('localhost', port).close
|
64
|
+
return true
|
65
|
+
rescue => e
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def pool_run(params, worker_count=10)
|
72
|
+
lock = Mutex.new
|
73
|
+
ths = []
|
74
|
+
ind = 0
|
75
|
+
worker_count.times do |i|
|
76
|
+
ths << Thread.new(i) do |tid|
|
77
|
+
loop do
|
78
|
+
param = nil
|
79
|
+
lock.synchronize do
|
80
|
+
Thread.exit if ind >= params.size
|
81
|
+
param = params[ind]
|
82
|
+
ind += 1
|
83
|
+
end
|
84
|
+
begin
|
85
|
+
yield(param, tid)
|
86
|
+
rescue => e
|
87
|
+
@logger.warn("pool_run error: #{e} from #{caller(1).first(3).join(";")}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
ths.each(&:join)
|
93
|
+
end
|
94
|
+
|
95
|
+
def monitor_all_instances
|
96
|
+
params = service_instances.map {|instance| instance }
|
97
|
+
lock = Mutex.new
|
98
|
+
failed_instances = []
|
99
|
+
pool_run(params) do |ins, _|
|
100
|
+
if !closing && ins.in_monitored?
|
101
|
+
if ins.running?
|
102
|
+
ins.failed_times = 0
|
103
|
+
else
|
104
|
+
ins.failed_times ||= 0
|
105
|
+
ins.failed_times += 1
|
106
|
+
if ins.in_monitored?
|
107
|
+
lock.synchronize { failed_instances << ins }
|
108
|
+
else
|
109
|
+
@logger.error("Instance #{ins.name} is failed too many times. Unmonitored.")
|
110
|
+
ins.stop
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
@logger.debug("Found failed_instances: #{failed_instances.map{|i| i.name}}") if failed_instances.size > 0
|
116
|
+
m_actions.each do |act|
|
117
|
+
unless closing
|
118
|
+
method = "#{act}_failed_instances"
|
119
|
+
if respond_to?(method.to_sym)
|
120
|
+
begin
|
121
|
+
send(method.to_sym, failed_instances)
|
122
|
+
rescue => e
|
123
|
+
@logger.warn("#{method}: #{e}")
|
124
|
+
end
|
125
|
+
else
|
126
|
+
@logger.warn("Failover action #{act} is not defined")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
rescue => e
|
131
|
+
@logger.warn("monitor_all_instances: #{e}")
|
132
|
+
ensure
|
133
|
+
setup_monitor_timer unless closing
|
134
|
+
end
|
135
|
+
|
136
|
+
def restart_failed_instances(failed_instances)
|
137
|
+
stop_instances(failed_instances)
|
138
|
+
start_instances(failed_instances)
|
139
|
+
end
|
140
|
+
|
141
|
+
def start_all_instances
|
142
|
+
start_instances(service_instances)
|
143
|
+
end
|
144
|
+
|
145
|
+
def start_instances(all_instances)
|
146
|
+
pool_run(all_instances, @instance_parallel_start_count || 10) do |instance|
|
147
|
+
next if closing
|
148
|
+
del_port(instance.port)
|
149
|
+
|
150
|
+
if instance.running?
|
151
|
+
@logger.warn("Service #{instance.name} already listening on port #{instance.port}")
|
152
|
+
next
|
153
|
+
end
|
154
|
+
|
155
|
+
unless instance.base_dir?
|
156
|
+
@logger.warn("Service #{instance.name} in local DB, but not in file system")
|
157
|
+
next
|
158
|
+
end
|
159
|
+
|
160
|
+
begin
|
161
|
+
instance.migration_check()
|
162
|
+
rescue => e
|
163
|
+
@logger.error("Error on migration_check: #{e}")
|
164
|
+
next
|
165
|
+
end
|
166
|
+
|
167
|
+
begin
|
168
|
+
instance.run(instance.start_options)
|
169
|
+
@logger.info("Successfully start provisioned instance #{instance.name}")
|
170
|
+
rescue => e
|
171
|
+
@logger.error("Error starting instance #{instance.name}: #{e}")
|
172
|
+
# Try to stop the instance since the container could be created
|
173
|
+
begin
|
174
|
+
instance.stop
|
175
|
+
rescue => e
|
176
|
+
# Ignore the rollback error and just record a warning log
|
177
|
+
@logger.warn("Error stopping instance #{instance.name} when rollback from a starting failure")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def stop_all_instances
|
184
|
+
stop_instances(service_instances)
|
185
|
+
end
|
186
|
+
|
187
|
+
def stop_instances(all_instances)
|
188
|
+
pool_run(all_instances, @instance_parallel_stop_count || 10) do |instance|
|
189
|
+
begin
|
190
|
+
instance.stop
|
191
|
+
@logger.info("Successfully stop instance #{instance.name}")
|
192
|
+
rescue => e
|
193
|
+
@logger.error("Error stopping instance #{instance.name}: #{e}")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def varz_details
|
199
|
+
varz = super
|
200
|
+
unmonitored = []
|
201
|
+
service_instances.each{|ins| unmonitored << ins.name unless ins.in_monitored? }
|
202
|
+
varz[:unmonitored] = unmonitored
|
203
|
+
varz
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,426 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
2
|
+
require 'instance_utils'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
class VCAP::Services::Base::Warden::Service
|
6
|
+
|
7
|
+
include VCAP::Services::Base::Utils
|
8
|
+
include VCAP::Services::Base::Warden::InstanceUtils
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def init(options)
|
13
|
+
@@options = options
|
14
|
+
@base_dir = options[:base_dir]
|
15
|
+
@log_dir = options[:service_log_dir]
|
16
|
+
@common_dir = options[:service_common_dir]
|
17
|
+
@bin_dir = options[:service_bin_dir]
|
18
|
+
@image_dir = options[:image_dir]
|
19
|
+
@logger = options[:logger]
|
20
|
+
@max_disk = options[:max_disk]
|
21
|
+
@max_memory = options[:max_memory]
|
22
|
+
@memory_overhead = options[:memory_overhead]
|
23
|
+
@quota = options[:filesystem_quota] || false
|
24
|
+
@service_start_timeout = options[:service_start_timeout] || 3
|
25
|
+
@service_status_timeout = options[:service_status_timeout] || 3
|
26
|
+
@bandwidth_per_second = options[:bandwidth_per_second]
|
27
|
+
@service_port = options[:service_port]
|
28
|
+
@rm_instance_dir_timeout = options[:rm_instance_dir_timeout] || 10
|
29
|
+
@m_failed_times = options[:m_failed_times] || 3
|
30
|
+
@warden_socket_path = options[:warden_socket_path] || "/tmp/warden.sock"
|
31
|
+
FileUtils.mkdir_p(File.dirname(options[:local_db].split(':')[1]))
|
32
|
+
DataMapper.setup(:default, options[:local_db])
|
33
|
+
DataMapper::auto_upgrade!
|
34
|
+
FileUtils.mkdir_p(base_dir)
|
35
|
+
FileUtils.mkdir_p(log_dir)
|
36
|
+
FileUtils.mkdir_p(image_dir) if @image_dir
|
37
|
+
@in_memory_status = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def define_im_properties(*args)
|
41
|
+
args.each do |prop|
|
42
|
+
define_method("#{prop}=".to_sym) do |value|
|
43
|
+
self.class.in_memory_status[self[:name]] ||= {}
|
44
|
+
self.class.in_memory_status[self[:name]][prop] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
define_method(prop) do
|
48
|
+
self.class.in_memory_status[self[:name]] && self.class.in_memory_status[self[:name]][prop]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :base_dir, :log_dir, :bin_dir, :common_dir, :image_dir, :max_disk, :logger, :quota,
|
54
|
+
:max_memory, :memory_overhead, :service_start_timeout, :service_status_timeout,
|
55
|
+
:bandwidth_per_second, :service_port, :rm_instance_dir_timeout, :m_failed_times,
|
56
|
+
:in_memory_status, :warden_socket_path
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
define_im_properties :failed_times
|
61
|
+
|
62
|
+
def in_monitored?
|
63
|
+
!failed_times || failed_times <= self.class.m_failed_times
|
64
|
+
end
|
65
|
+
|
66
|
+
def logger
|
67
|
+
self.class.logger
|
68
|
+
end
|
69
|
+
|
70
|
+
def prepare_filesystem(max_size, opts={})
|
71
|
+
if base_dir?
|
72
|
+
self.class.sh "umount #{base_dir}", :raise => false if self.class.quota
|
73
|
+
logger.warn("Service #{self[:name]} base_dir:#{base_dir} already exists, deleting it")
|
74
|
+
FileUtils.rm_rf(base_dir)
|
75
|
+
end
|
76
|
+
if log_dir?
|
77
|
+
logger.warn("Service #{self[:name]} log_dir:#{log_dir} already exists, deleting it")
|
78
|
+
FileUtils.rm_rf(log_dir)
|
79
|
+
end
|
80
|
+
if image_file?
|
81
|
+
logger.warn("Service #{self[:name]} image_file:#{image_file} already exists, deleting it")
|
82
|
+
FileUtils.rm_f(image_file)
|
83
|
+
end
|
84
|
+
FileUtils.mkdir_p(base_dir)
|
85
|
+
FileUtils.mkdir_p(data_dir)
|
86
|
+
FileUtils.mkdir_p(log_dir)
|
87
|
+
|
88
|
+
ext_opts = nil
|
89
|
+
ext_opts = "-E \"lazy_itable_init=1\"" if opts[:lazy_itable_init]
|
90
|
+
if self.class.quota
|
91
|
+
self.class.sh "dd if=/dev/null of=#{image_file} bs=1M seek=#{max_size.to_i}"
|
92
|
+
self.class.sh "mkfs.ext4 -q -F -O \"^has_journal,uninit_bg\" #{"#{ext_opts}" if ext_opts} #{image_file}"
|
93
|
+
loop_setup
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def loop_setdown
|
98
|
+
self.class.sh "umount #{base_dir}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def loop_setup
|
102
|
+
self.class.sh "mount -n -o loop #{image_file} #{base_dir}"
|
103
|
+
# Set the dir owner back to the process user
|
104
|
+
self.class.sh "chown -R #{Process.uid}:#{Process.gid} #{base_dir}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def loop_setup?
|
108
|
+
mounted = false
|
109
|
+
File.open("/proc/mounts", mode="r") do |f|
|
110
|
+
f.each do |w|
|
111
|
+
if Regexp.new(base_dir) =~ w
|
112
|
+
mounted = true
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
mounted
|
118
|
+
end
|
119
|
+
|
120
|
+
def need_loop_resize?
|
121
|
+
image_file? && File.size(image_file) != self.class.max_disk * 1024 * 1024
|
122
|
+
end
|
123
|
+
|
124
|
+
def loop_resize
|
125
|
+
loop_up = loop_setup?
|
126
|
+
loop_setdown if loop_up
|
127
|
+
self.class.sh "cp #{image_file} #{image_file}.bak"
|
128
|
+
begin
|
129
|
+
old_size = File.size(image_file)
|
130
|
+
self.class.sh "resize2fs -f #{image_file} #{self.class.max_disk.to_i}M"
|
131
|
+
logger.info("Service #{self[:name]} change loop file size from #{old_size / 1024 / 1024}M to #{self.class.max_disk}M")
|
132
|
+
rescue => e
|
133
|
+
# Revert image file to the backup if resize raise error
|
134
|
+
self.class.sh "cp #{image_file}.bak #{image_file}"
|
135
|
+
logger.error("Service #{self[:name]} revert image file for error #{e}")
|
136
|
+
ensure
|
137
|
+
self.class.sh "rm -f #{image_file}.bak"
|
138
|
+
end
|
139
|
+
loop_setup if loop_up
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_loopfile
|
143
|
+
self.class.sh "mv #{base_dir} #{base_dir+"_bak"}"
|
144
|
+
self.class.sh "mkdir -p #{base_dir}"
|
145
|
+
self.class.sh "A=`du -sm #{base_dir+"_bak"} | awk '{ print $1 }'`;A=$((A+32));if [ $A -lt #{self.class.max_disk.to_i} ]; then A=#{self.class.max_disk.to_i}; fi;dd if=/dev/null of=#{image_file} bs=1M seek=$A"
|
146
|
+
self.class.sh "mkfs.ext4 -q -F -O \"^has_journal,uninit_bg\" #{image_file}"
|
147
|
+
self.class.sh "mount -n -o loop #{image_file} #{base_dir}"
|
148
|
+
self.class.sh "cp -af #{base_dir+"_bak"}/* #{base_dir}", :timeout => 60.0
|
149
|
+
end
|
150
|
+
|
151
|
+
def migration_check
|
152
|
+
# incase for bosh --recreate, which will delete log dir
|
153
|
+
FileUtils.mkdir_p(base_dir) unless base_dir?
|
154
|
+
FileUtils.mkdir_p(log_dir) unless log_dir?
|
155
|
+
|
156
|
+
if image_file?
|
157
|
+
loop_resize if need_loop_resize?
|
158
|
+
unless loop_setup?
|
159
|
+
# for case where VM rebooted
|
160
|
+
logger.info("Service #{self[:name]} mounting data file")
|
161
|
+
loop_setup
|
162
|
+
end
|
163
|
+
else
|
164
|
+
if self.class.quota
|
165
|
+
logger.warn("Service #{self[:name]} need migration to quota")
|
166
|
+
to_loopfile
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def task(desc)
|
172
|
+
begin
|
173
|
+
yield
|
174
|
+
rescue => e
|
175
|
+
logger.error("Fail to #{desc}. Error: #{e}")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# instance operation helper
|
180
|
+
def delete
|
181
|
+
container_name = self[:container]
|
182
|
+
name = self[:name]
|
183
|
+
task "destroy record in local db" do
|
184
|
+
destroy! if saved?
|
185
|
+
end
|
186
|
+
|
187
|
+
task "delete in-memory status" do
|
188
|
+
self.class.in_memory_status.delete(name)
|
189
|
+
end
|
190
|
+
|
191
|
+
task "stop container when deleting service #{name}" do
|
192
|
+
stop(container_name)
|
193
|
+
end
|
194
|
+
|
195
|
+
task "delete instance directories" do
|
196
|
+
if self.class.quota
|
197
|
+
self.class.sh("rm -f #{image_file}", {:block => false})
|
198
|
+
end
|
199
|
+
# delete service data directory could be slow, so increase the timeout
|
200
|
+
self.class.sh("rm -rf #{base_dir} #{log_dir} #{util_dirs.join(' ')}", {:block => false, :timeout => self.class.rm_instance_dir_timeout})
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def run_command(handle, cmd_hash)
|
205
|
+
if cmd_hash[:use_spawn]
|
206
|
+
container_spawn_command(handle, cmd_hash[:script], cmd_hash[:use_root])
|
207
|
+
else
|
208
|
+
container_run_command(handle, cmd_hash[:script], cmd_hash[:use_root])
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# The logic in instance run function is:
|
213
|
+
# 0. To avoid to create orphan, clean up container if handle exists
|
214
|
+
# 1. Generate bind mount request and create warden container with bind mount options
|
215
|
+
# 2. Limit memory and bandwidth of the container (optional)
|
216
|
+
# 3. Run pre service start script (optional)
|
217
|
+
# 4. Run service start script
|
218
|
+
# 5. Create iptables rules for service process (optional)
|
219
|
+
# 6. Get container IP address and wait for the service finishing starting
|
220
|
+
# 7. Run post service start script (optional)
|
221
|
+
# 8. Run post service start block (optional)
|
222
|
+
# 9. Save the instance info to local db
|
223
|
+
def run(options=nil, &post_start_block)
|
224
|
+
stop if self[:container] && self[:container].length > 0
|
225
|
+
# If no options specified, then check whether the instance is stored in local db
|
226
|
+
# to decide to use which start options
|
227
|
+
options = (new? ? first_start_options : start_options) unless options
|
228
|
+
loop_setup if self.class.quota && (not loop_setup?)
|
229
|
+
bind_mounts = []
|
230
|
+
bind_mounts = options[:bind_dirs].map { |bind_dir| bind_mount_request(bind_dir) }
|
231
|
+
handle = container_start(bind_mounts)
|
232
|
+
self[:container] = handle
|
233
|
+
rw_dirs = options[:bind_dirs].map { |bind_dir| bind_dir[:dst] || bind_dir[:src] unless bind_dir[:read_only] }.compact
|
234
|
+
run_command(handle, {:script => "chown -R vcap:vcap #{rw_dirs.join(' ')}", :use_root => true}) unless rw_dirs.empty?
|
235
|
+
limit_memory(handle, memory_limit) if memory_limit
|
236
|
+
limit_bandwidth(handle, bandwidth_limit) if bandwidth_limit
|
237
|
+
run_command(handle, options[:pre_start_script]) if options[:pre_start_script]
|
238
|
+
run_command(handle, options[:start_script]) if options[:start_script]
|
239
|
+
map_port(handle, self[:port], options[:service_port]) if options[:need_map_port]
|
240
|
+
rsp = container_info(handle)
|
241
|
+
self[:ip] = rsp.container_ip
|
242
|
+
# Check whether the service finish starting,
|
243
|
+
# the check method can be different depends on whether the service is first start
|
244
|
+
raise VCAP::Services::Base::Error::ServiceError::new(VCAP::Services::Base::Error::ServiceError::SERVICE_START_TIMEOUT) unless wait_service_start(options[:service_start_timeout], options[:is_first_start])
|
245
|
+
run_command(handle, options[:post_start_script]) if options[:post_start_script]
|
246
|
+
# The post start block is some work that need do after first start in provision,
|
247
|
+
# where restart this instance, there should be no such work
|
248
|
+
post_start_block.call(self) if post_start_block
|
249
|
+
save!
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
253
|
+
def running?
|
254
|
+
finish_start?
|
255
|
+
end
|
256
|
+
|
257
|
+
# Usually, stop can retrieve container_name from local_db
|
258
|
+
# An exception is unprovision, which destroys record in local_db first.
|
259
|
+
def stop(container_name=nil)
|
260
|
+
name = container_name || self[:container]
|
261
|
+
if container_running?(name)
|
262
|
+
begin
|
263
|
+
run_command(name, stop_options[:stop_script]) if stop_options[:stop_script]
|
264
|
+
rescue => e
|
265
|
+
logger.error("Failed to call instance stop script #{stop_options[:stop_script]} with error #{e}")
|
266
|
+
end
|
267
|
+
container_stop(name)
|
268
|
+
container_destroy(name)
|
269
|
+
unless container_name
|
270
|
+
self[:container] = ''
|
271
|
+
save
|
272
|
+
end
|
273
|
+
loop_setdown if self.class.quota
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# directory helper
|
278
|
+
def image_file
|
279
|
+
return File.join(self.class.image_dir, "#{self[:name]}.img") if self.class.image_dir
|
280
|
+
''
|
281
|
+
end
|
282
|
+
|
283
|
+
def base_dir
|
284
|
+
return File.join(self.class.base_dir, self[:name]) if self.class.base_dir
|
285
|
+
''
|
286
|
+
end
|
287
|
+
|
288
|
+
def log_dir
|
289
|
+
return File.join(self.class.log_dir, self[:name]) if self.class.log_dir
|
290
|
+
''
|
291
|
+
end
|
292
|
+
|
293
|
+
def image_file?
|
294
|
+
File.exists?(image_file)
|
295
|
+
end
|
296
|
+
|
297
|
+
def base_dir?
|
298
|
+
Dir.exists?(base_dir)
|
299
|
+
end
|
300
|
+
|
301
|
+
def log_dir?
|
302
|
+
Dir.exists?(log_dir)
|
303
|
+
end
|
304
|
+
|
305
|
+
def util_dirs
|
306
|
+
[]
|
307
|
+
end
|
308
|
+
|
309
|
+
def data_dir
|
310
|
+
File.join(base_dir, "data")
|
311
|
+
end
|
312
|
+
|
313
|
+
def script_dir
|
314
|
+
File.join(self.class.common_dir, "bin")
|
315
|
+
end
|
316
|
+
|
317
|
+
def bin_dir
|
318
|
+
self.class.bin_dir[version]
|
319
|
+
end
|
320
|
+
|
321
|
+
def common_dir
|
322
|
+
self.class.common_dir
|
323
|
+
end
|
324
|
+
|
325
|
+
def update_bind_dirs(bind_dirs, old_bind, new_bind)
|
326
|
+
find_bind = bind_dirs.select { |bind| bind[:src] == old_bind[:src] && bind[:dst] == old_bind[:dst] && bind[:read_only] == old_bind[:read_only] }
|
327
|
+
unless find_bind.empty?
|
328
|
+
find_bind[0][:src] = new_bind[:src]
|
329
|
+
find_bind[0][:dst] = new_bind[:dst]
|
330
|
+
find_bind[0][:read_only] = new_bind[:read_only]
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# service start/stop helper
|
335
|
+
def wait_service_start(service_start_timeout, is_first_start=false)
|
336
|
+
(service_start_timeout * 10).times do
|
337
|
+
sleep 0.1
|
338
|
+
if is_first_start
|
339
|
+
return true if finish_first_start?
|
340
|
+
else
|
341
|
+
return true if finish_start?
|
342
|
+
end
|
343
|
+
end
|
344
|
+
false
|
345
|
+
end
|
346
|
+
|
347
|
+
### Service Node subclasses can override these following method ###
|
348
|
+
|
349
|
+
# Instance start options, basically the node need define ":start_script",
|
350
|
+
# and use other default options.
|
351
|
+
def start_options
|
352
|
+
bind_dirs = []
|
353
|
+
bind_dirs << {:src => bin_dir, :read_only => true}
|
354
|
+
bind_dirs << {:src => common_dir, :read_only => true}
|
355
|
+
# Since the script "warden_service_ctl" writes log in a hard-code directory "/var/vcap/sys/log/monit,
|
356
|
+
# then we need has this directory with write permission in warden container,
|
357
|
+
# now the work around is bind-mount instance log dir to "/var/vcap/sys/log/monit"
|
358
|
+
bind_dirs << {:src => log_dir, :dst => "/var/vcap/sys/log/monit"}
|
359
|
+
bind_dirs << {:src => base_dir}
|
360
|
+
bind_dirs << {:src => log_dir}
|
361
|
+
bind_dirs.concat util_dirs.map { |dir| {:src => dir} }
|
362
|
+
{
|
363
|
+
:service_port => self.class.service_port,
|
364
|
+
:need_map_port => true,
|
365
|
+
:is_first_start => false,
|
366
|
+
:bind_dirs => bind_dirs,
|
367
|
+
:service_start_timeout => self.class.service_start_timeout,
|
368
|
+
}
|
369
|
+
end
|
370
|
+
|
371
|
+
# It's the same with start_options except "is_first_start" key by default,
|
372
|
+
# but can be different for some services between instance provision (first start)
|
373
|
+
# and restart (normal start), then the node subclass need override this function
|
374
|
+
def first_start_options
|
375
|
+
options = start_options
|
376
|
+
options[:is_first_start] = true
|
377
|
+
options
|
378
|
+
end
|
379
|
+
|
380
|
+
# Check where the service process finish starting,
|
381
|
+
# the node subclass should override this function.
|
382
|
+
def finish_start?
|
383
|
+
true
|
384
|
+
end
|
385
|
+
|
386
|
+
# For some services the check methods in instance provision and restart are different,
|
387
|
+
# then they should override this method, otherwise the behavior is the same with first_start
|
388
|
+
def finish_first_start?
|
389
|
+
finish_start?
|
390
|
+
end
|
391
|
+
|
392
|
+
# Base user should provide a script to stop instance.
|
393
|
+
# if stop_options is empty, the process will get a SIGTERM first then SIGKILL later.
|
394
|
+
def stop_options
|
395
|
+
{
|
396
|
+
:stop_script => {:script => "#{service_script} stop #{base_dir} #{log_dir} #{common_dir}"},
|
397
|
+
}
|
398
|
+
end
|
399
|
+
|
400
|
+
# Provide a command to monitor the health of instance.
|
401
|
+
# if status_options is empty, running? method will only show the health of container
|
402
|
+
def status_options
|
403
|
+
{
|
404
|
+
:status_script => {:script => "#{service_script} status #{base_dir} #{log_dir} #{common_dir}"}
|
405
|
+
}
|
406
|
+
end
|
407
|
+
|
408
|
+
# Generally the node can use this default service script path
|
409
|
+
def service_script
|
410
|
+
File.join(script_dir, "warden_service_ctl")
|
411
|
+
end
|
412
|
+
|
413
|
+
# Generally the node can use this default calculation method for memory limitation
|
414
|
+
def memory_limit
|
415
|
+
if self.class.max_memory
|
416
|
+
(self.class.max_memory + (self.class.memory_overhead || 0)).to_i
|
417
|
+
else
|
418
|
+
nil
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
# Generally the node can use this default calculation method for bandwidth limitation
|
423
|
+
def bandwidth_limit
|
424
|
+
self.class.bandwidth_per_second
|
425
|
+
end
|
426
|
+
end
|