vcap_services_base 0.2.10
Sign up to get free protection for your applications and to get access to all the features.
- 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,158 @@
|
|
1
|
+
require "redis"
|
2
|
+
require "time"
|
3
|
+
require_relative "../service_error"
|
4
|
+
|
5
|
+
module VCAP::Services::Base::SnapshotV2
|
6
|
+
class SnapshotClient
|
7
|
+
include VCAP::Services::Base::Error
|
8
|
+
|
9
|
+
SNAPSHOT_KEY_PREFIX = "vcap:snapshotv2".freeze
|
10
|
+
SNAPSHOT_ID = "maxid".freeze
|
11
|
+
FILTER_KEYS = %w(snapshot_id date size name).freeze
|
12
|
+
MAX_NAME_LENGTH = 512
|
13
|
+
|
14
|
+
def initialize(redis_config)
|
15
|
+
@redis = ::Redis.new(redis_config)
|
16
|
+
# FIXME: use UUID?
|
17
|
+
redis_init
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_empty_snapshot(service_id, name)
|
21
|
+
snapshot = {
|
22
|
+
'state' => 'empty',
|
23
|
+
'size' => 0,
|
24
|
+
'name' => name,
|
25
|
+
'snapshot_id' => new_snapshot_id,
|
26
|
+
}
|
27
|
+
msg = Yajl::Encoder.encode(snapshot)
|
28
|
+
client.hset(redis_key(service_id), snapshot['snapshot_id'], msg)
|
29
|
+
snapshot
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get all snapshots related to a service instance
|
33
|
+
#
|
34
|
+
def service_snapshots(service_id)
|
35
|
+
return unless service_id
|
36
|
+
res = client.hgetall(redis_key(service_id))
|
37
|
+
res.values.map{|v| Yajl::Parser.parse(v)}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return total snapshots count
|
41
|
+
#
|
42
|
+
def service_snapshots_count(service_id)
|
43
|
+
return unless service_id
|
44
|
+
client.hlen(redis_key(service_id))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get detail information for a single snapshot
|
48
|
+
#
|
49
|
+
def snapshot_details(service_id, snapshot_id)
|
50
|
+
return unless service_id && snapshot_id
|
51
|
+
res = client.hget(redis_key(service_id), snapshot_id)
|
52
|
+
raise ServiceError.new(ServiceError::NOT_FOUND, "snapshot #{snapshot_id}") unless res
|
53
|
+
Yajl::Parser.parse(res)
|
54
|
+
end
|
55
|
+
|
56
|
+
# filter internal keys of a given snapshot object, return a new snapshot object in canonical format
|
57
|
+
def self.filter_keys(snapshot)
|
58
|
+
return unless snapshot.is_a? Hash
|
59
|
+
snapshot.select {|k,v| FILTER_KEYS.include? k.to_s}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generate a new unique id for a snapshot
|
63
|
+
def new_snapshot_id
|
64
|
+
client.incr(redis_key(SNAPSHOT_ID)).to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the snapshot file path that service should save the dump file to.
|
68
|
+
# the snapshot path structure looks like <base_dir>\snapshots\<service-name>\<aa>\<bb>\<cc>\<aabbcc-rest-of-instance-guid>\snapshot_id\<service specific data>
|
69
|
+
def self.snapshot_filepath(base_dir, service_name, service_id, snapshot_id)
|
70
|
+
File.join(base_dir, "snapshots", service_name, service_id[0,2], service_id[2,2], service_id[4,2], service_id, snapshot_id.to_s)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Update the name of given snapshot.
|
74
|
+
# This function is not protected by redis lock so a optimistic lock
|
75
|
+
# is applied to prevent concurrent update.
|
76
|
+
#
|
77
|
+
def update_name(service_id, snapshot_id, name)
|
78
|
+
return unless service_id && snapshot_id && name
|
79
|
+
verify_input_name(name)
|
80
|
+
|
81
|
+
key = self.class.redis_key(service_id)
|
82
|
+
# NOTE: idealy should watch on combination of (service_id, snapshot_id)
|
83
|
+
# but current design doesn't support such fine-grained watching.
|
84
|
+
client.watch(key)
|
85
|
+
|
86
|
+
snapshot = client.hget(redis_key(service_id), snapshot_id)
|
87
|
+
return nil unless snapshot
|
88
|
+
snapshot = Yajl::Parser.parse(snapshot)
|
89
|
+
snapshot["name"] = name
|
90
|
+
|
91
|
+
res = client.multi do
|
92
|
+
save_snapshot(service_id, snapshot)
|
93
|
+
end
|
94
|
+
|
95
|
+
unless res
|
96
|
+
raise ServiceError.new(ServiceError::REDIS_CONCURRENT_UPDATE)
|
97
|
+
end
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def save_snapshot(service_id , snapshot)
|
102
|
+
return unless service_id && snapshot
|
103
|
+
# FIXME: srsly? where are we using symbols?
|
104
|
+
sid = snapshot[:snapshot_id] || snapshot["snapshot_id"]
|
105
|
+
return unless sid
|
106
|
+
msg = Yajl::Encoder.encode(snapshot)
|
107
|
+
client.hset(redis_key(service_id), sid, msg)
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete_snapshot(service_id , snapshot_id)
|
111
|
+
return unless service_id && snapshot_id
|
112
|
+
client.hdel(redis_key(service_id), snapshot_id)
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def self.fmt_time()
|
117
|
+
# UTC time in ISO 8601 format.
|
118
|
+
Time.now.utc.strftime("%FT%TZ")
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.redis_key(key)
|
122
|
+
"#{SNAPSHOT_KEY_PREFIX}:#{key}"
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def filter_keys(snapshot)
|
127
|
+
self.class.filter_keys(snapshot)
|
128
|
+
end
|
129
|
+
|
130
|
+
def snapshot_filepath(base_dir, service_name, service_id, snapshot_id)
|
131
|
+
self.class.snapshot_filepath(base_dir, service_name, service_id, snapshot_id)
|
132
|
+
end
|
133
|
+
|
134
|
+
def redis_key(key)
|
135
|
+
self.class.redis_key(key)
|
136
|
+
end
|
137
|
+
|
138
|
+
attr_reader :redis
|
139
|
+
|
140
|
+
# initialize necessary keys
|
141
|
+
def redis_init
|
142
|
+
@redis.setnx("#{SNAPSHOT_KEY_PREFIX}:#{SNAPSHOT_ID}", 1)
|
143
|
+
end
|
144
|
+
|
145
|
+
def client
|
146
|
+
redis
|
147
|
+
end
|
148
|
+
|
149
|
+
def verify_input_name(name)
|
150
|
+
return unless name
|
151
|
+
|
152
|
+
raise ServiceError.new(ServiceError::INVALID_SNAPSHOT_NAME,
|
153
|
+
"Input name exceed the max allowed #{MAX_NAME_LENGTH} characters.") if name.size > MAX_NAME_LENGTH
|
154
|
+
|
155
|
+
#TODO: shall we sanitize the input?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "resque-status"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
require_relative "../service_error"
|
5
|
+
require_relative "snapshot_client"
|
6
|
+
|
7
|
+
|
8
|
+
module VCAP::Services::Base::SnapshotV2
|
9
|
+
class MySqlSnapshotJob
|
10
|
+
def self.queue_lookup_key
|
11
|
+
'mysql'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# common utils for snapshot job
|
16
|
+
class SnapshotJob
|
17
|
+
include Resque::Plugins::Status
|
18
|
+
|
19
|
+
class QueueResolver
|
20
|
+
extend Forwardable
|
21
|
+
def_delegator :@job_class, :queue_lookup_key
|
22
|
+
|
23
|
+
def initialize(job_class)
|
24
|
+
@job_class = job_class
|
25
|
+
end
|
26
|
+
|
27
|
+
def resolve(*args)
|
28
|
+
result = nil
|
29
|
+
args.each do |arg|
|
30
|
+
result = arg[queue_lookup_key] if (arg.is_a? Hash) && (arg.has_key?(queue_lookup_key))
|
31
|
+
end
|
32
|
+
raise "no queue matched for look up key #{queue_lookup_key} and args #{args}" unless result
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def queue_lookup_key
|
39
|
+
:node_id
|
40
|
+
end
|
41
|
+
|
42
|
+
def select_queue(*args)
|
43
|
+
QueueResolver.new(self).resolve(*args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(*args)
|
48
|
+
super(*args)
|
49
|
+
parse_config
|
50
|
+
#client = SnapshotClient.new(Config.redis_config)
|
51
|
+
# @logger = Config.logger
|
52
|
+
# Snapshot.redis_connect
|
53
|
+
end
|
54
|
+
|
55
|
+
# def fmt_error(e)
|
56
|
+
# "#{e}: [#{e.backtrace.join(" | ")}]"
|
57
|
+
# end
|
58
|
+
|
59
|
+
# def required_options(*args)
|
60
|
+
# missing_opts = args.select{|arg| !options.has_key? arg.to_s}
|
61
|
+
# raise ArgumentError, "Missing #{missing_opts.join(', ')} in options: #{options.inspect}" unless missing_opts.empty?
|
62
|
+
# end
|
63
|
+
|
64
|
+
# def create_lock(lock_name)
|
65
|
+
# # lock_name = "lock:lifecycle:#{name}"
|
66
|
+
# ttl = @config['job_ttl'] || 600
|
67
|
+
# lock = Lock.new(lock_name, :logger => @logger, :ttl => ttl)
|
68
|
+
# lock
|
69
|
+
# end
|
70
|
+
|
71
|
+
# def get_dump_path(name, snapshot_id)
|
72
|
+
# snapshot_filepath(@config["snapshots_base_dir"], @config["service_name"], name, snapshot_id)
|
73
|
+
# end
|
74
|
+
|
75
|
+
# def cleanup(name, snapshot_id)
|
76
|
+
# return unless name && snapshot_id
|
77
|
+
# @logger.info("Clean up snapshot and files for #{name}, snapshot id: #{snapshot_id}")
|
78
|
+
# client.delete_snapshot(name, snapshot_id)
|
79
|
+
# FileUtils.rm_rf(get_dump_path(name, snapshot_id))
|
80
|
+
# end
|
81
|
+
|
82
|
+
# def handle_error(e)
|
83
|
+
# @logger.error("Error in #{self.class} uuid:#{@uuid}: #{fmt_error(e)}")
|
84
|
+
# err = (e.instance_of?(ServiceError)? e : ServiceError.new(ServiceError::INTERNAL_ERROR)).to_hash
|
85
|
+
# err_msg = Yajl::Encoder.encode(err["msg"])
|
86
|
+
# failed(err_msg)
|
87
|
+
# end
|
88
|
+
|
89
|
+
def parse_config
|
90
|
+
@config = Yajl::Parser.parse(ENV['WORKER_CONFIG'])
|
91
|
+
raise "Need environment variable: WORKER_CONFIG" unless @config
|
92
|
+
@config
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/base/utils.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module VCAP::Services::Base::Utils
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def sh(*args)
|
11
|
+
options = args[-1].respond_to?(:to_hash) ? args.pop.to_hash: {}
|
12
|
+
options = { :timeout => 5.0, :max => 1024 * 1024, :sudo => true, :block => true }.merge(options)
|
13
|
+
arg = options[:sudo] == false ? args[0] : "sudo " << args[0]
|
14
|
+
|
15
|
+
begin
|
16
|
+
stdin, stdout, stderr, status = Open3.popen3(arg)
|
17
|
+
pid = status[:pid]
|
18
|
+
out_buf = ""
|
19
|
+
err_buf = ""
|
20
|
+
if options[:block]
|
21
|
+
start = Time.now
|
22
|
+
# Manually ping the process per second to check whether the process is alive or not
|
23
|
+
while (Time.now - start) < options[:timeout] && status.alive?
|
24
|
+
begin
|
25
|
+
out_buf << stdout.read_nonblock(4096)
|
26
|
+
err_buf << stderr.read_nonblock(4096)
|
27
|
+
rescue IO::WaitReadable, EOFError
|
28
|
+
end
|
29
|
+
sleep 0.2
|
30
|
+
end
|
31
|
+
|
32
|
+
if status.alive?
|
33
|
+
Process.kill("TERM", pid)
|
34
|
+
Process.detach(pid)
|
35
|
+
raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} timed out:\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}"
|
36
|
+
end
|
37
|
+
exit_status = status.value.exitstatus
|
38
|
+
raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} exited with #{status.value.exitstatus}:\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}" unless exit_status == 0
|
39
|
+
exit_status
|
40
|
+
else
|
41
|
+
# If the work is still not done after timeout, then kill the process and record an erorr log
|
42
|
+
Thread.new do
|
43
|
+
sleep options[:timeout]
|
44
|
+
if status.alive?
|
45
|
+
Process.kill("TERM", pid)
|
46
|
+
Process.detach(pid)
|
47
|
+
logger.error "sh #{args} executed with pid #{pid} timed out" if logger
|
48
|
+
else
|
49
|
+
logger.error "sh #{args} executed with failure, the exit status is #{status.value.exitstatus}" if status.value.exitstatus != 0 && logger
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return 0
|
53
|
+
end
|
54
|
+
rescue Errno::EPERM
|
55
|
+
raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} cannot be killed (privilege issue?):\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}"
|
56
|
+
rescue Errno::ESRCH
|
57
|
+
raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} does not exist:\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}"
|
58
|
+
rescue => e
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/base/version.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
require "warden/client"
|
3
|
+
require "warden/protocol"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.join("..", File.dirname(__FILE__))
|
6
|
+
require "base/utils"
|
7
|
+
require "base/abstract"
|
8
|
+
require "base/service_error"
|
9
|
+
|
10
|
+
module VCAP
|
11
|
+
module Services
|
12
|
+
module Base
|
13
|
+
module Warden
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module VCAP::Services::Base::Warden::InstanceUtils
|
20
|
+
|
21
|
+
def self.included(base)
|
22
|
+
base.extend(ClassMethods)
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def warden_connect
|
27
|
+
warden_client = Warden::Client.new(warden_socket_path)
|
28
|
+
warden_client.connect
|
29
|
+
warden_client
|
30
|
+
end
|
31
|
+
|
32
|
+
def warden_socket_path
|
33
|
+
"/tmp/warden.sock"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# warden container operation helper
|
38
|
+
def container_start(bind_mounts=[])
|
39
|
+
warden = self.class.warden_connect
|
40
|
+
req = Warden::Protocol::CreateRequest.new
|
41
|
+
unless bind_mounts.empty?
|
42
|
+
req.bind_mounts = bind_mounts
|
43
|
+
end
|
44
|
+
rsp = warden.call(req)
|
45
|
+
handle = rsp.handle
|
46
|
+
handle
|
47
|
+
ensure
|
48
|
+
warden.disconnect if warden
|
49
|
+
end
|
50
|
+
|
51
|
+
def container_stop(handle, force=true)
|
52
|
+
warden = self.class.warden_connect
|
53
|
+
req = Warden::Protocol::StopRequest.new
|
54
|
+
req.handle = handle
|
55
|
+
req.background = !force
|
56
|
+
warden.call(req)
|
57
|
+
true
|
58
|
+
ensure
|
59
|
+
warden.disconnect if warden
|
60
|
+
end
|
61
|
+
|
62
|
+
def container_destroy(handle)
|
63
|
+
warden = self.class.warden_connect
|
64
|
+
req = Warden::Protocol::DestroyRequest.new
|
65
|
+
req.handle = handle
|
66
|
+
warden.call(req)
|
67
|
+
true
|
68
|
+
ensure
|
69
|
+
warden.disconnect if warden
|
70
|
+
end
|
71
|
+
|
72
|
+
def container_running?(handle)
|
73
|
+
handle != "" && container_info(handle) != nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def container_run_command(handle, cmd, is_privileged=false)
|
77
|
+
warden = self.class.warden_connect
|
78
|
+
req = Warden::Protocol::RunRequest.new
|
79
|
+
req.handle = handle
|
80
|
+
req.script = cmd
|
81
|
+
req.privileged = is_privileged
|
82
|
+
res = warden.call(req)
|
83
|
+
if res.exit_status == 0
|
84
|
+
res
|
85
|
+
else
|
86
|
+
raise VCAP::Services::Base::Error::ServiceError::new(VCAP::Services::Base::Error::ServiceError::WARDEN_RUN_COMMAND_FAILURE, cmd, handle, res.exit_status, res.stdout, res.stderr)
|
87
|
+
end
|
88
|
+
ensure
|
89
|
+
warden.disconnect if warden
|
90
|
+
end
|
91
|
+
|
92
|
+
def container_spawn_command(handle, cmd, is_privileged=false)
|
93
|
+
warden = self.class.warden_connect
|
94
|
+
req = Warden::Protocol::SpawnRequest.new
|
95
|
+
req.handle = handle
|
96
|
+
req.script = cmd
|
97
|
+
req.privileged = is_privileged
|
98
|
+
res = warden.call(req)
|
99
|
+
res
|
100
|
+
ensure
|
101
|
+
warden.disconnect if warden
|
102
|
+
end
|
103
|
+
|
104
|
+
def container_info(handle)
|
105
|
+
warden = self.class.warden_connect
|
106
|
+
req = Warden::Protocol::InfoRequest.new
|
107
|
+
req.handle = handle
|
108
|
+
warden.call(req)
|
109
|
+
rescue => e
|
110
|
+
nil
|
111
|
+
ensure
|
112
|
+
warden.disconnect if warden
|
113
|
+
end
|
114
|
+
|
115
|
+
def limit_memory(handle, limit)
|
116
|
+
warden = self.class.warden_connect
|
117
|
+
req = Warden::Protocol::LimitMemoryRequest.new
|
118
|
+
req.handle = handle
|
119
|
+
req.limit_in_bytes = limit * 1024 * 1024
|
120
|
+
warden.call(req)
|
121
|
+
true
|
122
|
+
ensure
|
123
|
+
warden.disconnect if warden
|
124
|
+
end
|
125
|
+
|
126
|
+
def limit_bandwidth(handle, rate)
|
127
|
+
warden = self.class.warden_connect
|
128
|
+
req = Warden::Protocol::LimitBandwidthRequest.new
|
129
|
+
req.handle = handle
|
130
|
+
req.rate = (rate * 1024 * 1024).to_i
|
131
|
+
req.burst = (rate * 1 * 1024 * 1024).to_i # Set burst the same size as rate
|
132
|
+
warden.call(req)
|
133
|
+
true
|
134
|
+
ensure
|
135
|
+
warden.disconnect if warden
|
136
|
+
end
|
137
|
+
|
138
|
+
def map_port(handle, src_port, dest_port)
|
139
|
+
warden = self.class.warden_connect
|
140
|
+
req = Warden::Protocol::NetInRequest.new
|
141
|
+
req.handle = handle
|
142
|
+
req.host_port = src_port
|
143
|
+
req.container_port = dest_port
|
144
|
+
res = warden.call(req)
|
145
|
+
res
|
146
|
+
ensure
|
147
|
+
warden.disconnect if warden
|
148
|
+
end
|
149
|
+
|
150
|
+
def bind_mount_request(bind_dir)
|
151
|
+
bind = Warden::Protocol::CreateRequest::BindMount.new
|
152
|
+
bind.src_path = bind_dir[:src]
|
153
|
+
bind.dst_path = bind_dir[:dst] || bind_dir[:src]
|
154
|
+
if bind_dir[:read_only]
|
155
|
+
bind.mode = Warden::Protocol::CreateRequest::BindMount::Mode::RO
|
156
|
+
else
|
157
|
+
bind.mode = Warden::Protocol::CreateRequest::BindMount::Mode::RW
|
158
|
+
end
|
159
|
+
bind
|
160
|
+
end
|
161
|
+
end
|