warden/lib/warden/container/linux.rb
# coding: UTF-8
require "warden/container/base"
require "warden/container/features/cgroup"
require "warden/container/features/mem_limit"
require "warden/container/features/net"
require "warden/container/features/quota"
require "warden/errors"
require "shellwords"
module Warden
module Container
class Linux < Base
include Features::Cgroup
include Features::Net
include Features::MemLimit
include Features::Quota
class << self
attr_reader :bind_mount_script_template
def setup(config)
unless Process.uid == 0
raise WardenError.new("linux containers require root privileges")
end
super(config)
unless File.directory?(container_rootfs_path)
raise WardenError.new("container_rootfs_path does not exist #{container_rootfs_path}")
end
unless File.directory?(container_depot_path)
raise WardenError.new("container_depot_path does not exist #{container_depot_path}")
end
options = {
:env => {
"POOL_NETWORK" => config.network["pool_network"],
"ALLOW_NETWORKS" => allow_networks.join(" "),
"ALLOW_HOST_ACCESS" => config.network["allow_host_access"].to_s,
"CONTAINER_ROOTFS_PATH" => container_rootfs_path,
"CONTAINER_DEPOT_PATH" => container_depot_path,
"CONTAINER_DEPOT_MOUNT_POINT_PATH" => container_depot_mount_point_path,
"DISK_QUOTA_ENABLED" => disk_quota_enabled.to_s,
},
}
sh File.join(root_path, "setup.sh"), options
end
def alive?(path)
socket_path = File.join(path, "run", "wshd.sock")
begin
socket = UNIXSocket.new(socket_path)
rescue Errno::ECONNREFUSED
# wshd is not running
return false
rescue Errno::ENOENT
# unix socket is missing
return false
else
socket.close
end
true
end
end
def env
{
"id" => container_id,
"network_host_ip" => host_ip.to_human,
"network_container_ip" => container_ip.to_human,
"network_netmask" => self.class.network_pool.pooled_netmask.to_human,
"user_uid" => uid,
"rootfs_path" => container_rootfs_path,
"allow_nested_warden" => Server.config.allow_nested_warden?.to_s,
"container_iface_mtu" => container_iface_mtu,
"dns_servers" => Server.config.network["dns_servers"].join("\n"),
}
end
def do_create(request, response)
options = { :env => env.dup }
if request.rootfs
unless Dir.exist? request.rootfs
raise WardenError.new("rootfs #{request.rootfs} not found")
end
options[:env]["rootfs_path"] = request.rootfs
end
sh File.join(root_path, "create.sh"), container_path, options
logger.debug("Container created")
write_bind_mount_commands(request)
logger.debug2("Wrote bind mount commands")
sh File.join(container_path, "start.sh"), options
logger.debug("Container started")
nil
end
def do_stop(request, response)
args = [File.join(container_path, "stop.sh")]
args += ["-w", "0"] if request.kill
sh *args
nil
end
def do_destroy(request, response)
sh File.join(container_path, "stop.sh"), "-w", "0", raise: false
sh File.join(root_path, "destroy.sh"), container_path
logger.debug("Container destroyed")
nil
end
def create_job(request)
wsh_path = File.join(bin_path, "wsh")
socket_path = File.join(container_path, "run", "wshd.sock")
user = request.privileged ? "root" : "vcap"
# Build arguments
args = [wsh_path]
args += ["--socket", socket_path]
args += ["--user", user]
args += ["/bin/bash"]
lang = { "LANG" => ENV['LANG'] || "en_US.UTF-8" }
options = {
:input => request.script,
:env => resource_limits(request).merge(lang),
}
args << options
run_options = {
discard_output: request.discard_output,
syslog_socket: Server.config.server["syslog_socket"],
log_tag: request.log_tag,
}
spawn_job(run_options, *args)
end
def do_copy_in(request, response)
src_path = request.src_path
dst_path = request.dst_path
perform_rsync(src_path, "vcap@container:#{dst_path}")
nil
end
def do_copy_out(request, response)
src_path = request.src_path
dst_path = request.dst_path
perform_rsync("vcap@container:#{src_path}", dst_path)
if request.owner
sh "chown", "-R", request.owner, dst_path
end
nil
end
private
def perform_rsync(src_path, dst_path)
wsh_path = File.join(bin_path, "wsh")
socket_path = File.join(container_path, "run", "wshd.sock")
# Build arguments
args = ["rsync"]
args += ["-e", "#{wsh_path} --socket #{socket_path} --rsh"]
args += ["-r"] # Recursive copy
args += ["-p"] # Preserve permissions
args += ["--links"] # Preserve symlinks
args += [src_path, dst_path]
sh *args
end
def add_bind_mount(file, src_path, dst_path, mode)
dst_path = File.join(container_path, "mnt", dst_path[1..-1])
file.puts "mkdir -p #{dst_path}"
file.puts "mount -n --bind #{src_path} #{dst_path}"
file.puts "mount -n --bind -o remount,#{mode} #{src_path} #{dst_path}"
end
def write_bind_mount_commands(request)
File.open(File.join(container_path, "lib", "hook-child-before-pivot.sh"), "a") do |file|
file.puts
file.puts
if request.bind_mounts.respond_to?(:each)
request.bind_mounts.each do |bind_mount|
src_path = bind_mount.src_path
dst_path = bind_mount.dst_path
# Check that the source path exists
stat = File.stat(src_path) rescue nil
raise WardenError.new("Source path for bind mount does not exist: #{src_path}") if stat.nil?
mode = case bind_mount.mode
when Protocol::CreateRequest::BindMount::Mode::RO
"ro"
when Protocol::CreateRequest::BindMount::Mode::RW
"rw"
else
raise "Unknown mode"
end
add_bind_mount(file, src_path, dst_path, mode)
end
end
end
end
end
end
end