-
1
module Xhyve
-
# Parse DHCP leases file for a MAC address, and get its ip.
-
1
module DHCP
-
1
extend self
-
1
LEASES_FILE = '/var/db/dhcpd_leases'
-
1
WAIT_TIME = 1
-
1
MAX_ATTEMPTS = 60
-
-
1
def get_ip_for_mac(mac)
-
14
normalized_mac = normalize_mac(mac)
-
-
14
max = ENV.key?('MAX_IP_WAIT') ? ENV['MAX_IP_WAIT'].to_i : nil
-
14
ip = wait_for(max: max) do
-
16
ip = parse_lease_file_for_mac(normalized_mac)
-
end
-
end
-
-
1
def parse_lease_file_for_mac(mac)
-
16
lease_file = (ENV['LEASES_FILE'] || LEASES_FILE)
-
16
contents = wait_for do
-
16
File.read(lease_file) if File.exists?(lease_file)
-
end
-
16
pattern = contents.match(/ip_address=(\S+)\n\thw_address=\d+,#{mac}/)
-
16
if pattern
-
13
addrs = pattern.captures
-
13
addrs.first if addrs
-
end
-
end
-
-
1
private
-
-
1
def wait_for(max: nil)
-
30
attempts = 0
-
30
max ||= MAX_ATTEMPTS
-
30
while attempts < max
-
32
attempts += 1
-
32
result = yield
-
32
return result if result
-
3
sleep(WAIT_TIME)
-
end
-
end
-
-
# macos dhcp represents mac addresses differently from xhyve. Specifically,
-
# it doesn't display leading zeros. This function normalized the mac
-
# address to the macos format
-
1
def normalize_mac(mac)
-
# don't try to normalize if it doesn't seem like a mac...
-
14
return mac if mac !~ /.*:.*:.*:.*:.*:.*/
-
13
mac_parts = mac.to_s.split(":")
-
91
normalized_parts = mac_parts.map {|s| Integer(s, 16).to_s(16) }
-
13
normalized_parts.join(":")
-
end
-
end
-
end
-
1
require 'securerandom'
-
1
require 'io/console'
-
-
1
require 'xhyve/dhcp'
-
-
1
module Xhyve
-
1
BINARY_PATH = File.expand_path('../../../lib/xhyve/vendor/xhyve', __FILE__).freeze
-
-
# An object to represent a guest that we can start and stop
-
# Effectively, it's a command wrapper around xhyve to provide an
-
# object oriented interface to a hypervisor guest
-
1
class Guest
-
1
PCI_BASE = 3
-
1
NULLDEV = '/dev/null'
-
-
1
attr_reader :pid, :uuid, :mac
-
-
1
def initialize(**opts)
-
1
@kernel = opts.fetch(:kernel)
-
1
@initrd = opts.fetch(:initrd)
-
1
@cmdline = opts.fetch(:cmdline)
-
1
@blockdevs = [opts[:blockdevs] || []].flatten
-
1
@memory = opts[:memory] || '500M'
-
1
@processors = opts[:processors] || '1'
-
1
@uuid = opts[:uuid] || SecureRandom.uuid
-
1
@serial = opts[:serial] || 'com1'
-
1
@acpi = opts.fetch(:acpi, true)
-
1
@networking = opts.fetch(:networking, true)
-
1
@foreground = opts[:foreground] || false
-
1
@binary = opts[:binary] || BINARY_PATH
-
1
@command = build_command
-
1
@mac = find_mac
-
end
-
-
1
def start
-
1
outfile, infile = redirection
-
1
@pid = spawn(@command, [:out, :err] => outfile, in: infile)
-
1
if @foreground
-
Process.wait(@pid)
-
outfile.cooked!
-
infile.cooked!
-
end
-
1
@pid
-
end
-
-
1
def stop
-
1
Process.kill('KILL', @pid)
-
end
-
-
1
def running?
-
1
(true if Process.kill(0, @pid) rescue false)
-
end
-
-
1
def ip
-
6
@ip ||= Xhyve::DHCP.get_ip_for_mac(@mac)
-
end
-
-
1
private
-
-
1
def redirection
-
1
if @foreground
-
[$stdout.raw!, $stdin.raw! ]
-
else
-
1
[NULLDEV, NULLDEV]
-
end
-
end
-
-
1
def find_mac
-
1
`#{@command} -M`.strip.gsub(/MAC:\s+/,'')
-
end
-
-
1
def build_command
-
[
-
"#{@binary}",
-
"#{'-A' if @acpi}",
-
'-U', @uuid,
-
'-m', @memory,
-
'-c', @processors,
-
'-s', '0:0,hostbridge',
-
"#{"-s #{PCI_BASE - 1}:0,virtio-net" if @networking }" ,
-
1
"#{"#{@blockdevs.each_with_index.map { |p, i| "-s #{PCI_BASE + i},virtio-blk,#{p}" }.join(' ')}" unless @blockdevs.empty? }",
-
'-s', '31,lpc',
-
'-l', "#{@serial},stdio",
-
'-f' "kexec,#{@kernel},#{@initrd},'#{@cmdline}'"
-
1
].join(' ')
-
end
-
end
-
end
-
1
require File.expand_path('../../spec_helper.rb', __FILE__)
-
-
RSpec.describe Xhyve::Guest do
-
1
before :all do
-
1
kernel = File.join(FIXTURE_PATH, 'guest', 'vmlinuz')
-
1
initrd = File.join(FIXTURE_PATH, 'guest', 'initrd')
-
1
blockdev = File.join(FIXTURE_PATH, 'guest', 'loop.img')
-
1
cmdline = 'earlyprintk=true console=ttyS0 user=console opt=vda tce=vda'
-
1
uuid = SecureRandom.uuid # '32e54269-d1e2-4bdf-b4ff-bbe0eb42572d' #
-
-
1
@guest = Xhyve::Guest.new(kernel: kernel, initrd: initrd, cmdline: cmdline, blockdevs: blockdev, uuid: uuid, serial: 'com1')
-
1
@guest.start
-
end
-
-
1
after :all do
-
1
@guest.stop
-
end
-
-
1
it 'Can start a guest' do
-
1
expect(@guest.pid).to_not be_nil
-
1
expect(@guest.pid).to be > 0
-
1
expect(@guest.running?).to eq(true)
-
end
-
-
1
it 'Can get the MAC of a guest' do
-
1
expect(@guest.mac).to_not be_nil
-
1
expect(@guest.mac).to_not be_empty
-
1
expect(@guest.mac).to match(/\w\w:\w\w:\w\w:\w\w:\w\w:\w\w/)
-
end
-
-
1
it 'Can get the IP of a guest' do
-
1
expect(@guest.ip).to_not be_nil
-
1
expect(@guest.ip).to match(/\d+\.+\d+\.\d+\.\d+/)
-
end
-
-
1
it 'Can ping the guest' do
-
1
expect(ping(@guest.ip)).to eq(true)
-
end
-
-
1
it 'Can ssh to the guest' do
-
1
expect(on_guest(@guest.ip, 'hostname')).to eq('box')
-
end
-
-
1
it 'Correctly sets processors' do
-
1
expect(on_guest(@guest.ip, "cat /proc/cpuinfo | grep 'cpu cores' | awk '{print $4}'")).to eq('1')
-
end
-
-
1
it 'Correctly sets memory' do
-
1
expect(on_guest(@guest.ip, "free -mm | grep 'Mem:' | awk '{print $2}'").to_i).to be_within(50).of(500)
-
end
-
1
end unless ENV['TRAVIS']