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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/base/abstract.rb +11 -0
  3. data/lib/base/api/message.rb +31 -0
  4. data/lib/base/asynchronous_service_gateway.rb +529 -0
  5. data/lib/base/backup.rb +206 -0
  6. data/lib/base/barrier.rb +54 -0
  7. data/lib/base/base.rb +159 -0
  8. data/lib/base/base_async_gateway.rb +164 -0
  9. data/lib/base/base_job.rb +5 -0
  10. data/lib/base/catalog_manager_base.rb +67 -0
  11. data/lib/base/catalog_manager_v1.rb +225 -0
  12. data/lib/base/catalog_manager_v2.rb +291 -0
  13. data/lib/base/cloud_controller_services.rb +75 -0
  14. data/lib/base/datamapper_l.rb +148 -0
  15. data/lib/base/gateway.rb +167 -0
  16. data/lib/base/gateway_service_catalog.rb +68 -0
  17. data/lib/base/http_handler.rb +101 -0
  18. data/lib/base/job/async_job.rb +71 -0
  19. data/lib/base/job/config.rb +27 -0
  20. data/lib/base/job/lock.rb +153 -0
  21. data/lib/base/job/package.rb +112 -0
  22. data/lib/base/job/serialization.rb +365 -0
  23. data/lib/base/job/snapshot.rb +354 -0
  24. data/lib/base/node.rb +471 -0
  25. data/lib/base/node_bin.rb +154 -0
  26. data/lib/base/plan.rb +63 -0
  27. data/lib/base/provisioner.rb +1120 -0
  28. data/lib/base/provisioner_v1.rb +125 -0
  29. data/lib/base/provisioner_v2.rb +193 -0
  30. data/lib/base/service.rb +93 -0
  31. data/lib/base/service_advertiser.rb +184 -0
  32. data/lib/base/service_error.rb +122 -0
  33. data/lib/base/service_message.rb +94 -0
  34. data/lib/base/service_plan_change_set.rb +11 -0
  35. data/lib/base/simple_aop.rb +63 -0
  36. data/lib/base/snapshot_v2/snapshot.rb +227 -0
  37. data/lib/base/snapshot_v2/snapshot_client.rb +158 -0
  38. data/lib/base/snapshot_v2/snapshot_job.rb +95 -0
  39. data/lib/base/utils.rb +63 -0
  40. data/lib/base/version.rb +7 -0
  41. data/lib/base/warden/instance_utils.rb +161 -0
  42. data/lib/base/warden/node_utils.rb +205 -0
  43. data/lib/base/warden/service.rb +426 -0
  44. data/lib/base/worker_bin.rb +76 -0
  45. data/lib/vcap_services_base.rb +16 -0
  46. 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