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,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
|