Class: Lxc

Inherits:
Object
  • Object
show all
Defined in:
lib/vagabond/cookbooks/lxc/libraries/lxc.rb

Defined Under Namespace

Classes: CommandFailed

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, args = {}) ⇒ Lxc

name

name of container

args

Argument hash

- :base_path -> path to container directory
- :dnsmasq_lease_file -> path to lease file


81
82
83
84
85
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 81

def initialize(name, args={})
  @name = name
  @base_path = args[:base_path] || '/var/lib/lxc'
  @lease_file = args[:dnsmasq_lease_file] || '/var/lib/misc/dnsmasq.leases'
end

Class Attribute Details

.use_sudoObject

Returns the value of attribute use_sudo.



9
10
11
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 9

def use_sudo
  @use_sudo
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



5
6
7
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 5

def name
  @name
end

Class Method Details

.connection_alive?(ip) ⇒ Boolean

ip

IP address

Returns if IP address is alive

Returns:

  • (Boolean)


71
72
73
74
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 71

def connection_alive?(ip)
  %x{ping -c 1 -W 1 #{ip}}
  $?.exitstatus == 0
end

.exists?(name) ⇒ Boolean

name

name of container

Returns if container exists

Returns:

  • (Boolean)


37
38
39
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 37

def exists?(name)
  list.include?(name)
end

.frozenObject

List frozen containers



31
32
33
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 31

def frozen
  full_list[:frozen]
end

.full_listObject

Return full container information list



59
60
61
62
63
64
65
66
67
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 59

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) ⇒ Object

name

Name of container

Returns information about given container



48
49
50
51
52
53
54
55
56
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 48

def info(name)
  res = {:state => nil, :pid => nil}
  info = %x{#{sudo}lxc-info -n #{name}}.split("\n")
  parts = info.first.split(' ')
  res[:state] = parts.last.downcase.to_sym
  parts = info.last.split(' ')
  res[:pid] = parts.last.to_i
  res
end

.listObject

List of containers



42
43
44
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 42

def list
  %x{#{sudo}lxc-ls}.split("\n").uniq
end

.runningObject

List running containers



21
22
23
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 21

def running
  full_list[:running]
end

.stoppedObject

List stopped containers



26
27
28
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 26

def stopped
  full_list[:stopped]
end

.sudoObject



11
12
13
14
15
16
17
18
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 11

def sudo
  case use_sudo
  when TrueClass
    'sudo '
  when String
    "#{use_sudo} "
  end
end

Instance Method Details

#container_command(cmd, retries = 1) ⇒ Object

cmd

Shell command string

retries

Number of retry attempts (1 second sleep interval)

Runs command in container via ssh



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 295

def container_command(cmd, retries=1)
  begin
    knife_container(cmd, container_ip(5))
  rescue => e
    if(retries.to_i > 0)
      Chef::Log.info "Encountered error running container command (#{cmd}): #{e}"
      Chef::Log.info "Retrying command..."
      retries = retries.to_i - 1
      sleep(1)
      retry
    else
      raise e
    end
  end
end

#container_configObject Also known as: config

Full path to container configuration file



197
198
199
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 197

def container_config
  File.join(container_path, 'config')
end

#container_ip(retries = 0, raise_on_fail = false) ⇒ Object

retries

Number of discovery attempt (3 second sleep intervals)

Returns container IP



109
110
111
112
113
114
115
116
117
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 109

def container_ip(retries=0, raise_on_fail=false)
  (retries.to_i + 1).times do
    ip = proc_detected_address || hw_detected_address || leased_address || lxc_stored_address
    return ip if ip && self.class.connection_alive?(ip)
    Chef::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_pathObject Also known as: path

Full path to container



191
192
193
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 191

def container_path
  File.join(@base_path, name)
end

#container_rootfsObject Also known as: rootfs



202
203
204
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 202

def container_rootfs
  File.join(container_path, 'rootfs')
end

#exists?Boolean

Returns if container exists

Returns:

  • (Boolean)


88
89
90
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 88

def exists?
  self.class.exists?(name)
end

#expand_path(path) ⇒ Object



207
208
209
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 207

def expand_path(path)
  File.join(container_rootfs, path)
end

#freezeObject

Freeze the container



232
233
234
235
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 232

def freeze
  run_command("#{sudo}lxc-freeze -n #{name}")
  run_command("#{sudo}lxc-wait -n #{name} -s FROZEN", :allow_failure_retry => 2)
end

#frozen?Boolean

Returns if container is frozen

Returns:

  • (Boolean)


103
104
105
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 103

def frozen?
  self.class.info(name)[:state] == :frozen
end

#hw_detected_addressObject



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 153

def hw_detected_address
  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
      Chef::Log.info "LXC Discovery: Found container address via HW addr: #{ip}"
      ip
    end
  end
end

#knife_container(cmd, ip) ⇒ Object

Raises:



253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 253

def knife_container(cmd, ip)
  require 'chef/knife/ssh'
  Chef::Knife::Ssh.load_deps
  k = Chef::Knife::Ssh.new([
    ip, '-m', '-i', '/opt/hw-lxc-config/id_rsa', '--no-host-key-verify', cmd
  ])
  e = nil
  begin
    e = k.run
  rescue SystemExit => e
  end
  raise CommandFailed.new(cmd) if e.nil? || e != 0
end

#leased_addressObject

Container address via dnsmasq lease



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 135

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
    Chef::Log.info "LXC Discovery: Found container address via DHCP lease: #{ip}"
    ip
  end
end

#lxc_stored_addressObject

Container address via lxc config file



120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 120

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
      Chef::Log.info "LXC Discovery: Found container address via storage: #{ip}"
      ip
    end
  end
end

#pidObject



215
216
217
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 215

def pid
  self.class.info(name)[:pid]
end

#proc_detected_address(base = '/run/netns') ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 171

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}")
      ip = res.strip.split(' ')[1].to_s.sub(%r{/.*$}, '').strip
      ip.empty? ? nil : ip
    end
  end
end

#run_command(cmd, args = {}) ⇒ Object

Simple helper to shell out



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 268

def run_command(cmd, args={})
  retries = args[:allow_failure_retry].to_i
  begin
    shlout = Mixlib::ShellOut.new(cmd, 
      :logger => Chef::Log.logger, 
      :live_stream => STDOUT,
      :timeout => args[:timeout] || 1200
    )
    shlout.run_command
    shlout.error!
  rescue Mixlib::ShellOut::ShellCommandFailed, CommandFailed, Mixlib::ShellOut::CommandTimeout
    if(args[:allow_failure])
      true
    elsif(retries > 0)
      Chef::Log.warn "LXC run command failed: #{cmd}"
      Chef::Log.warn "Retrying command. #{args[:allow_failure_retry].to_i - retries} of #{args[:allow_failure_retry].to_i} retries remain"
      retries -= 1
      retry
    else
      raise
    end
  end
end

#running?Boolean

Returns if container is running

Returns:

  • (Boolean)


93
94
95
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 93

def running?
  self.class.info(name)[:state] == :running
end

#shutdownObject

Shutdown the container



244
245
246
247
248
249
250
251
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 244

def shutdown
  run_command("#{sudo}lxc-shutdown -n #{name}")
  run_command("#{sudo}lxc-wait -n #{name} -s STOPPED", :allow_failure => true, :timeout => 10)
  if(running?)
    container_command('shutdown -h now')
    run_command("#{sudo}lxc-wait -n #{name} -s STOPPED")
  end
end

#startObject

Start the container



220
221
222
223
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 220

def start
  run_command("#{sudo}lxc-start -n #{name} -d")
  run_command("#{sudo}lxc-wait -n #{name} -s RUNNING", :allow_failure_retry => 2)
end

#stateObject



211
212
213
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 211

def state
  self.class.info(name)[:state]
end

#stopObject

Stop the container



226
227
228
229
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 226

def stop
  run_command("#{sudo}lxc-stop -n #{name}")
  run_command("#{sudo}lxc-wait -n #{name} -s STOPPED", :allow_failure_retry => 2)
end

#stopped?Boolean

Returns if container is stopped

Returns:

  • (Boolean)


98
99
100
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 98

def stopped?
  self.class.info(name)[:state] == :stopped
end

#sudoObject



186
187
188
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 186

def sudo
  self.class.sudo
end

#unfreezeObject

Unfreeze the container



238
239
240
241
# File 'lib/vagabond/cookbooks/lxc/libraries/lxc.rb', line 238

def unfreeze
  run_command("#{sudo}lxc-unfreeze -n #{name}")
  run_command("#{sudo}lxc-wait -n #{name} -s RUNNING", :allow_failure_retry => 2)
end