Class: Lxc
- Inherits:
-
Object
- Object
- Lxc
- Extended by:
- Helpers
- Includes:
- Helpers
- Defined in:
- lib/elecksee.rb,
lib/elecksee/lxc.rb,
lib/elecksee/clone.rb,
lib/elecksee/helpers.rb,
lib/elecksee/storage.rb,
lib/elecksee/ephemeral.rb,
lib/elecksee/helpers/copies.rb,
lib/elecksee/helpers/options.rb,
lib/elecksee/lxc_file_config.rb,
lib/elecksee/storage/overlay_mount.rb,
lib/elecksee/storage/virtual_device.rb,
lib/elecksee/storage/overlay_directory.rb
Overview
LXC interface
Defined Under Namespace
Modules: Helpers, Storage Classes: Clone, CommandFailed, CommandResult, Ephemeral, FileConfig, Pathname, Timeout
Class Attribute Summary collapse
-
.base_path ⇒ String
Base path for containers.
-
.container_command_via ⇒ Symbol
Default command method.
-
.default_ssh_key ⇒ String, NilClass
Path to default ssh key.
-
.default_ssh_password ⇒ String, NilClass
Default ssh password.
-
.default_ssh_user ⇒ String, NilClass
Default ssh user.
-
.shellout_helper ⇒ Symbol
:mixlib_shellout or :childprocess.
-
.use_sudo ⇒ Truthy, String
Use sudo when required (set to string for custom sudo command).
Instance Attribute Summary collapse
-
#base_path ⇒ String
readonly
Base path of container.
-
#lease_file ⇒ String
readonly
Path to dnsmasq lease file.
-
#name ⇒ String
readonly
Name of container.
-
#preferred_device ⇒ String
readonly
Network device to use for ssh connection.
-
#ssh_key ⇒ String, NilClass
Path to default ssh key.
-
#ssh_password ⇒ String, NilClass
Ssh password.
-
#ssh_user ⇒ String, NilClass
Ssh user.
Class Method Summary collapse
-
.connection_alive?(ip) ⇒ TrueClass, FalseClass
IP address is currently active.
-
.exists?(name) ⇒ TrueClass, FalseClass
Container currently exists.
-
.frozen ⇒ Array<String>
Currently frozen container names.
-
.full_list ⇒ Hash
Full list of containers grouped by state.
-
.info(name) ⇒ Hash
Information available for given container.
-
.list ⇒ Array<String>
List of all containers.
-
.running ⇒ Array<String>
Currently running container names.
-
.stopped ⇒ Array<String>
Currently stopped container names.
-
.sudo ⇒ String
Sudo command.
Instance Method Summary collapse
-
#connection(args = {}) ⇒ Rye::Box
Provide connection to running container.
-
#container_command(cmd, retries = 1) ⇒ CommandResult
Run command within container.
-
#container_config ⇒ Pathname
(also: #config)
Path to configuration file.
-
#container_ip(retries = 0, raise_on_fail = false) ⇒ String, NilClass
Current IP address of container.
-
#container_path ⇒ Pathname
(also: #path)
Path to container.
-
#container_rootfs ⇒ Pathname
(also: #rootfs)
Path to rootfs.
-
#destroy ⇒ self
Destroy the container.
-
#direct_container_command(command, args = {}) ⇒ CommandResult
(also: #knife_container)
Execute command within running container.
-
#execute(command, opts = {}) ⇒ CommandResult
Execute command within the container.
-
#exists? ⇒ TrueClass, FalseClass
Container exists.
-
#expand_path(path) ⇒ Pathname
Expand path within containers rootfs.
-
#freeze ⇒ self
Freeze the container.
-
#frozen? ⇒ TrueClass, FalseClass
Container is currently frozen.
-
#hw_detected_address ⇒ String, NilClass
Container address discovered via device.
-
#info_detected_address ⇒ String, NilClass
Container address discovered via info.
-
#initialize(name, args = {}) ⇒ Lxc
constructor
Create new instance.
-
#leased_address ⇒ String, NilClass
Container address discovered via dnsmasq lease.
-
#lxc_stored_address ⇒ String, NilClass
Container address defined within the container’s config file.
-
#pid ⇒ Integer, Symbol
Process ID or :unknown.
-
#proc_detected_address(base = '/run/netns') ⇒ String, NilClass
Container address discovered via process.
-
#running? ⇒ TrueClass, FalseClass
Container is currently running.
-
#shutdown ⇒ self
Shutdown the container.
-
#start(*args) ⇒ self
Start the container.
-
#state ⇒ Symbol
Current state.
-
#stop ⇒ self
Stop the container.
-
#stopped? ⇒ TrueClass, FalseClass
Container is currently stopped.
-
#tmp_execute_script(command, opts) {|command_script| ... } ⇒ Object
Write command to temporary file with networking enablement wrapper and yield relative path.
-
#unfreeze ⇒ self
Unfreeze the container.
-
#wait_for_state(desired_state, args = {}) ⇒ self
Wait for container to reach given state.
Methods included from Helpers
child_process_command, detect_home, log, mixlib_shellout_command, run_command, sudo
Constructor Details
#initialize(name, args = {}) ⇒ Lxc
Create new instance
171 172 173 174 175 176 177 178 179 |
# File 'lib/elecksee/lxc.rb', line 171 def initialize(name, args={}) @name = name @base_path = args[:base_path] || self.class.base_path @lease_file = args[:dnsmasq_lease_file] || '/var/lib/misc/dnsmasq.leases' @preferred_device = args[:net_device] @ssh_key = args.fetch(:ssh_key, self.class.default_ssh_key) @ssh_password = args.fetch(:ssh_password, self.class.default_ssh_password) @ssh_user = args.fetch(:ssh_user, self.class.default_ssh_user) end |
Class Attribute Details
.base_path ⇒ String
Returns base path for containers.
47 48 49 |
# File 'lib/elecksee/lxc.rb', line 47 def base_path @base_path end |
.container_command_via ⇒ Symbol
Returns default command method.
57 58 59 |
# File 'lib/elecksee/lxc.rb', line 57 def container_command_via @container_command_via end |
.default_ssh_key ⇒ String, NilClass
Returns path to default ssh key.
51 52 53 |
# File 'lib/elecksee/lxc.rb', line 51 def default_ssh_key @default_ssh_key end |
.default_ssh_password ⇒ String, NilClass
Returns default ssh password.
53 54 55 |
# File 'lib/elecksee/lxc.rb', line 53 def default_ssh_password @default_ssh_password end |
.default_ssh_user ⇒ String, NilClass
Returns default ssh user.
55 56 57 |
# File 'lib/elecksee/lxc.rb', line 55 def default_ssh_user @default_ssh_user end |
.shellout_helper ⇒ Symbol
Returns :mixlib_shellout or :childprocess.
49 50 51 |
# File 'lib/elecksee/lxc.rb', line 49 def shellout_helper @shellout_helper end |
.use_sudo ⇒ Truthy, String
Returns use sudo when required (set to string for custom sudo command).
45 46 47 |
# File 'lib/elecksee/lxc.rb', line 45 def use_sudo @use_sudo end |
Instance Attribute Details
#base_path ⇒ String (readonly)
Returns base path of container.
28 29 30 |
# File 'lib/elecksee/lxc.rb', line 28 def base_path @base_path end |
#lease_file ⇒ String (readonly)
Returns path to dnsmasq lease file.
30 31 32 |
# File 'lib/elecksee/lxc.rb', line 30 def lease_file @lease_file end |
#name ⇒ String (readonly)
Returns name of container.
26 27 28 |
# File 'lib/elecksee/lxc.rb', line 26 def name @name end |
#preferred_device ⇒ String (readonly)
Returns network device to use for ssh connection.
32 33 34 |
# File 'lib/elecksee/lxc.rb', line 32 def preferred_device @preferred_device end |
#ssh_key ⇒ String, NilClass
Returns path to default ssh key.
34 35 36 |
# File 'lib/elecksee/lxc.rb', line 34 def ssh_key @ssh_key end |
#ssh_password ⇒ String, NilClass
Returns ssh password.
36 37 38 |
# File 'lib/elecksee/lxc.rb', line 36 def ssh_password @ssh_password end |
#ssh_user ⇒ String, NilClass
Returns ssh user.
38 39 40 |
# File 'lib/elecksee/lxc.rb', line 38 def ssh_user @ssh_user end |
Class Method Details
.connection_alive?(ip) ⇒ TrueClass, FalseClass
IP address is currently active
155 156 157 158 |
# File 'lib/elecksee/lxc.rb', line 155 def connection_alive?(ip) %x{ping -c 1 -W 1 #{ip}} $?.exitstatus == 0 end |
.exists?(name) ⇒ TrueClass, FalseClass
Container currently exists
99 100 101 |
# File 'lib/elecksee/lxc.rb', line 99 def exists?(name) list.include?(name) end |
.frozen ⇒ Array<String>
Currently frozen container names
91 92 93 |
# File 'lib/elecksee/lxc.rb', line 91 def frozen full_list[:frozen] end |
.full_list ⇒ Hash
Full list of containers grouped by state
141 142 143 144 145 146 147 148 149 |
# File 'lib/elecksee/lxc.rb', line 141 def full_list res = {} list.each do |item| item_info = info(item) res[item_info[:state]] ||= [] res[item_info[:state]] << item end res end |
.info(name) ⇒ Hash
Information available for given container
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/elecksee/lxc.rb', line 115 def info(name) if(exists?(name)) info = run_command("#{sudo}lxc-info -n #{name}", :allow_failure_retry => 3, :allow_failure => true) end if(info) Hash[ info.stdout.split("\n").map do |string| string.split(': ').map(&:strip) end.map do |key, value| key = key.tr(' ', '_').downcase.to_sym if(key == :state) value = value.downcase.to_sym elsif(value.to_i.to_s == value) value = value.to_i end [key, value] end ] else Hash[:state, :unknown, :pid, -1] end end |
.list ⇒ Array<String>
List of all containers
106 107 108 109 |
# File 'lib/elecksee/lxc.rb', line 106 def list run_command('lxc-ls', :sudo => true). stdout.split(/\s/).map(&:strip).compact end |
.running ⇒ Array<String>
Currently running container names
77 78 79 |
# File 'lib/elecksee/lxc.rb', line 77 def running full_list[:running] end |
.stopped ⇒ Array<String>
Currently stopped container names
84 85 86 |
# File 'lib/elecksee/lxc.rb', line 84 def stopped full_list[:stopped] end |
.sudo ⇒ String
Returns sudo command.
60 61 62 63 64 65 66 67 |
# File 'lib/elecksee/lxc.rb', line 60 def sudo case use_sudo when TrueClass 'sudo ' when String "#{use_sudo} " end end |
Instance Method Details
#connection(args = {}) ⇒ Rye::Box
Provide connection to running container
464 465 466 467 468 469 470 471 472 473 |
# File 'lib/elecksee/lxc.rb', line 464 def connection(args={}) Rye::Box.new(args.fetch(:ip, container_ip(3)), :user => ssh_user, :password => ssh_password, :password_prompt => false, :keys => [ssh_key], :safe => false, :paranoid => false ) end |
#container_command(cmd, retries = 1) ⇒ CommandResult
retries are over 1 second intervals
Run command within container
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 |
# File 'lib/elecksee/lxc.rb', line 566 def container_command(cmd, retries=1) begin detect_home(true) direct_container_command(cmd, :ip => container_ip(5), :live_stream => STDOUT, :raise_on_failure => true ) rescue => e if(retries.to_i > 0) log.info "Encountered error running container command (#{cmd}): #{e}" log.info "Retrying command..." retries = retries.to_i - 1 sleep(1) retry else raise e end end end |
#container_config ⇒ Pathname Also known as: config
Returns path to configuration file.
330 331 332 |
# File 'lib/elecksee/lxc.rb', line 330 def container_config container_path.join('config') end |
#container_ip(retries = 0, raise_on_fail = false) ⇒ String, NilClass
retries are executed on 3 second sleep intervals
Current IP address of container
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/elecksee/lxc.rb', line 207 def container_ip(retries=0, raise_on_fail=false) (retries.to_i + 1).times do ip = info_detected_address || proc_detected_address || hw_detected_address || leased_address || lxc_stored_address if(ip.is_a?(Array)) # Filter any found loopbacks ip.delete_if{|info| info[:device].start_with?('lo') } ip = ip.detect do |info| if(@preferred_device) info[:device] == @preferred_device else true end end ip = ip[:address] if ip end return ip if ip && self.class.connection_alive?(ip) log.warn "LXC IP discovery: Failed to detect live IP" sleep(3) if retries > 0 end raise "Failed to detect live IP address for container: #{name}" if raise_on_fail end |
#container_path ⇒ Pathname Also known as: path
Returns path to container.
324 325 326 |
# File 'lib/elecksee/lxc.rb', line 324 def container_path Pathname.new(@base_path).join(name) end |
#container_rootfs ⇒ Pathname Also known as: rootfs
Returns path to rootfs.
336 337 338 339 340 341 342 343 |
# File 'lib/elecksee/lxc.rb', line 336 def container_rootfs if(File.exists?(config)) r_path = File.readlines(config).detect do |line| line.start_with?('lxc.rootfs') end.to_s.split('=').last.to_s.strip end r_path.to_s.empty? ? container_path.join('rootfs') : Pathname.new(r_path) end |
#destroy ⇒ self
Destroy the container
428 429 430 431 432 433 434 |
# File 'lib/elecksee/lxc.rb', line 428 def destroy unless stopped? stop end run_command("lxc-destroy -n #{name}", :sudo => true) self end |
#direct_container_command(command, args = {}) ⇒ CommandResult Also known as: knife_container
Execute command within running container
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/elecksee/lxc.rb', line 483 def direct_container_command(command, args={}) if(args.fetch(:run_as, Lxc.container_command_via).to_sym == :ssh) begin result = connection(args).execute command CommandResult.new(result) rescue Rye::Err => e if(args[:raise_on_failure]) raise CommandFailed.new( "Command failed: #{command}", CommandResult.new(e) ) else false end end else command( "lxc-attach -n #{name} #{command}", args.merge(:sudo => true) ) end end |
#execute(command, opts = {}) ⇒ CommandResult
container must be stopped
Execute command within the container
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
# File 'lib/elecksee/lxc.rb', line 442 def execute(command, opts={}) if(stopped?) cmd = Shellwords.split(command) result = nil begin tmp_execute_script(command, opts) do |script_path| result = run_command("lxc-execute -n #{name} -- #{script_path}", :sudo => true) end rescue => e if(e.result.stderr.downcase.include?('failed to find an lxc-init')) $stderr.puts "ERROR: Missing `lxc-init` installation on container (#{name}). Install lxc-init on container before using `#execute`!" end raise end else raise "Cannot execute against running container (#{name})" end end |
#exists? ⇒ TrueClass, FalseClass
Returns container exists.
182 183 184 |
# File 'lib/elecksee/lxc.rb', line 182 def exists? self.class.exists?(name) end |
#expand_path(path) ⇒ Pathname
Expand path within containers rootfs
350 351 352 |
# File 'lib/elecksee/lxc.rb', line 350 def (path) container_rootfs.join(path) end |
#freeze ⇒ self
Freeze the container
390 391 392 393 394 |
# File 'lib/elecksee/lxc.rb', line 390 def freeze run_command("lxc-freeze -n #{name}", :sudo => true) wait_for_state(:frozen) self end |
#frozen? ⇒ TrueClass, FalseClass
Returns container is currently frozen.
197 198 199 |
# File 'lib/elecksee/lxc.rb', line 197 def frozen? self.class.info(name)[:state] == :frozen end |
#hw_detected_address ⇒ String, NilClass
Container address discovered via device
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/elecksee/lxc.rb', line 281 def hw_detected_address if(container_config.readable?) hw = File.readlines(container_config).detect{|line| line.include?('hwaddr') }.to_s.split('=').last.to_s.downcase if(File.exists?(container_config) && !hw.empty?) running? # need to do a list! ip = File.readlines('/proc/net/arp').detect{|line| line.downcase.include?(hw) }.to_s.split(' ').first.to_s.strip if(ip.to_s.empty?) nil else log.info "LXC Discovery: Found container address via HW addr: #{ip}" ip end end end end |
#info_detected_address ⇒ String, NilClass
Container address discovered via info
274 275 276 |
# File 'lib/elecksee/lxc.rb', line 274 def info_detected_address self.class.info(name)[:ip] end |
#leased_address ⇒ String, NilClass
Container address discovered via dnsmasq lease
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/elecksee/lxc.rb', line 253 def leased_address ip = nil if(File.exists?(@lease_file)) leases = File.readlines(@lease_file).map{|line| line.split(' ')} leases.each do |lease| if(lease.include?(name)) ip = lease[2] end end end if(ip.to_s.empty?) nil else log.info "LXC Discovery: Found container address via DHCP lease: #{ip}" ip end end |
#lxc_stored_address ⇒ String, NilClass
Container address defined within the container’s config file
236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/elecksee/lxc.rb', line 236 def lxc_stored_address if(File.exists?(container_config)) ip = File.readlines(container_config).detect{|line| line.include?('ipv4') }.to_s.split('=').last.to_s.strip if(ip.to_s.empty?) nil else log.info "LXC Discovery: Found container address via storage: #{ip}" ip end end end |
#pid ⇒ Integer, Symbol
Returns process ID or :unknown.
360 361 362 |
# File 'lib/elecksee/lxc.rb', line 360 def pid self.class.info(name)[:pid] end |
#proc_detected_address(base = '/run/netns') ⇒ String, NilClass
Container address discovered via process
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/elecksee/lxc.rb', line 305 def proc_detected_address(base='/run/netns') if(pid != -1) Dir.mktmpdir do |t_dir| name = File.basename(t_dir) path = File.join(base, name) system("#{sudo}mkdir -p #{base}") system("#{sudo}ln -s /proc/#{pid}/ns/net #{path}") res = %x{#{sudo}ip netns exec #{name} ip -4 addr show scope global | grep inet} system("#{sudo}rm -f #{path}") ips = res.split("\n").map do |line| parts = line.split(' ') {:address => parts[1].to_s.sub(%r{/.+$}, ''), :device => parts.last} end ips.empty? ? nil : ips end end end |
#running? ⇒ TrueClass, FalseClass
Returns container is currently running.
187 188 189 |
# File 'lib/elecksee/lxc.rb', line 187 def running? self.class.info(name)[:state] == :running end |
#shutdown ⇒ self
Shutdown the container
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/elecksee/lxc.rb', line 408 def shutdown # This block is for fedora/centos/anyone else that does not like lxc-shutdown if(running?) container_command('shutdown -h now') wait_for_state(:stopped, :timeout => 120) # If still running here, something is wrong if(running?) run_command("lxc-stop -n #{name}", :sudo => true) wait_for_state(:stopped, :timeout => 120) if(running?) raise "Failed to shutdown container: #{name}" end end end self end |
#start(*args) ⇒ self
Start the container
368 369 370 371 372 373 374 375 376 |
# File 'lib/elecksee/lxc.rb', line 368 def start(*args) if(args.include?(:no_daemon)) run_command("lxc-start -n #{name}", :sudo => true) else run_command("lxc-start -n #{name} -d", :sudo => true) wait_for_state(:running) end self end |
#state ⇒ Symbol
Returns current state.
355 356 357 |
# File 'lib/elecksee/lxc.rb', line 355 def state self.class.info(name)[:state] end |
#stop ⇒ self
Stop the container
381 382 383 384 385 |
# File 'lib/elecksee/lxc.rb', line 381 def stop run_command("lxc-stop -n #{name}", :allow_failure_retry => 3, :sudo => true) wait_for_state([:stopped, :unknown]) self end |
#stopped? ⇒ TrueClass, FalseClass
Returns container is currently stopped.
192 193 194 |
# File 'lib/elecksee/lxc.rb', line 192 def stopped? self.class.info(name)[:state] == :stopped end |
#tmp_execute_script(command, opts) {|command_script| ... } ⇒ Object
Write command to temporary file with networking enablement wrapper and yield relative path
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 |
# File 'lib/elecksee/lxc.rb', line 534 def tmp_execute_script(command, opts) script_path = "tmp/#{SecureRandom.uuid}" File.open(rootfs.join(script_path), 'w') do |file| file.puts '#!/bin/sh' unless(opts[:networking] == false) file.write <<-EOS /etc/network/if-pre-up.d/bridge > /dev/null 2>&1 ifdown eth0 > /dev/null 2>&1 ifup eth0 > /dev/null 2>&1 EOS end file.puts command file.puts "RESULT=$?" unless(opts[:networking] == false) file.puts "ifdown eth0 > /dev/null 2>&1" end file.puts "exit $RESULT" end FileUtils.chmod(0755, rootfs.join(script_path)) begin yield "/#{script_path}" ensure FileUtils.rm(rootfs.join(script_path)) end end |
#unfreeze ⇒ self
Unfreeze the container
399 400 401 402 403 |
# File 'lib/elecksee/lxc.rb', line 399 def unfreeze run_command("lxc-unfreeze -n #{name}", :sudo => true) wait_for_state(:running) self end |
#wait_for_state(desired_state, args = {}) ⇒ self
Wait for container to reach given state
514 515 516 517 518 519 520 521 522 523 |
# File 'lib/elecksee/lxc.rb', line 514 def wait_for_state(desired_state, args={}) args[:sleep_interval] ||= 1.0 wait_total = 0.0 desired_state = [desired_state].flatten.compact.map(&:to_sym) until(desired_state.include?(state) || (args[:timeout].to_i > 0 && wait_total.to_i > args[:timeout].to_i)) sleep(args[:sleep_interval]) wait_total += args[:sleep_interval] end self end |