Initial commit

This commit is contained in:
pradels
2013-03-27 00:55:30 +01:00
commit 810f0b31b9
41 changed files with 1809 additions and 0 deletions

30
lib/vagrant-libvirt.rb Normal file
View File

@@ -0,0 +1,30 @@
require 'pathname'
require 'vagrant-libvirt/plugin'
module VagrantPlugins
module Libvirt
lib_path = Pathname.new(File.expand_path("../vagrant-libvirt", __FILE__))
autoload :Action, lib_path.join("action")
autoload :Errors, lib_path.join("errors")
autoload :Util, lib_path.join("util")
# Hold connection handler so there is no need to connect more times than
# one. This can be annoying when there are more machines to create, or when
# doing state action first and then some other.
#
# TODO Don't sure if this is the best solution
@@libvirt_connection = nil
def self.libvirt_connection
@@libvirt_connection
end
def self.libvirt_connection=(conn)
@@libvirt_connection = conn
end
def self.source_root
@source_root ||= Pathname.new(File.expand_path("../../", __FILE__))
end
end
end

View File

@@ -0,0 +1,94 @@
require 'vagrant/action/builder'
module VagrantPlugins
module Libvirt
module Action
# Include the built-in modules so we can use them as top-level things.
include Vagrant::Action::Builtin
# This action is called to bring the box up from nothing.
def self.action_up
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use ConnectLibvirt
b.use Call, IsCreated do |env, b2|
if env[:result]
b2.use MessageAlreadyCreated
next
end
b2.use SetNameOfDomain
b2.use HandleStoragePool
b2.use HandleBoxImage
b2.use CreateDomainVolume
b2.use CreateDomain
b2.use CreateNetworkInterfaces
end
b.use TimedProvision
b.use StartDomain
b.use WaitTillUp
b.use SyncFolders
end
end
# This is the action that is primarily responsible for completely
# freeing the resources of the underlying virtual machine.
def self.action_destroy
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use Call, IsCreated do |env, b2|
if !env[:result]
b2.use MessageNotCreated
next
end
b2.use ConnectLibvirt
b2.use DestroyDomain
end
end
end
# This action is called to read the state of the machine. The resulting
# state is expected to be put into the `:machine_state_id` key.
def self.action_read_state
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use ConnectLibvirt
b.use ReadState
end
end
# This action is called to read the SSH info of the machine. The
# resulting state is expected to be put into the `:machine_ssh_info`
# key.
def self.action_read_ssh_info
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use ConnectLibvirt
b.use ReadSSHInfo
end
end
action_root = Pathname.new(File.expand_path("../action", __FILE__))
autoload :ConnectLibvirt, action_root.join("connect_libvirt")
autoload :IsCreated, action_root.join("is_created")
autoload :MessageAlreadyCreated, action_root.join("message_already_created")
autoload :MessageNotCreated, action_root.join("message_not_created")
autoload :HandleStoragePool, action_root.join("handle_storage_pool")
autoload :HandleBoxImage, action_root.join("handle_box_image")
autoload :SetNameOfDomain, action_root.join("set_name_of_domain")
autoload :CreateDomainVolume, action_root.join("create_domain_volume")
autoload :CreateDomain, action_root.join("create_domain")
autoload :CreateNetworkInterfaces, action_root.join("create_network_interfaces")
autoload :DestroyDomain, action_root.join("destroy_domain")
autoload :StartDomain, action_root.join("start_domain")
autoload :ReadState, action_root.join("read_state")
autoload :ReadSSHInfo, action_root.join("read_ssh_info")
autoload :TimedProvision, action_root.join("timed_provision")
autoload :WaitTillUp, action_root.join("wait_till_up")
autoload :SyncFolders, action_root.join("sync_folders")
end
end
end

View File

@@ -0,0 +1,72 @@
require 'fog'
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
class ConnectLibvirt
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::connect_libvirt")
@app = app
end
def call(env)
# If already connected to libvirt, just use it and don't connect
# again.
if Libvirt.libvirt_connection
env[:libvirt_compute] = Libvirt.libvirt_connection
return @app.call(env)
end
# Get config options for libvirt provider.
config = env[:machine].provider_config
# Setup connection uri.
uri = config.driver
if config.connect_via_ssh
uri << '+ssh://'
if config.username
uri << config.username + '@'
end
if config.host
uri << config.host
else
uri << 'localhost'
end
else
uri << '://'
uri << config.host if config.host
end
uri << '/system?no_verify=1'
conn_attr = {}
conn_attr[:provider] = 'libvirt'
conn_attr[:libvirt_uri] = uri
conn_attr[:libvirt_username] = config.username if config.username
conn_attr[:libvirt_password] = config.password if config.password
# Setup command for retrieving IP address for newly created machine
# with some MAC address. Get it via arp table. This solution doesn't
# require arpwatch to be installed.
conn_attr[:libvirt_ip_command] = "arp -an | grep $mac | sed '"
conn_attr[:libvirt_ip_command] << 's/.*(\([0-9\.]*\)).*/\1/'
conn_attr[:libvirt_ip_command] << "'"
@logger.info("Connecting to Libvirt (#{uri}) ...")
begin
env[:libvirt_compute] = Fog::Compute.new(conn_attr)
rescue Fog::Errors::Error => e
raise Errors::FogLibvirtConnectionError,
:error_message => e.message
end
Libvirt.libvirt_connection = env[:libvirt_compute]
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,62 @@
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
class CreateDomain
include VagrantPlugins::Libvirt::Util::ErbTemplate
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::create_domain")
@app = app
end
def call(env)
# Gather some info about domain
# TODO from Vagrantfile
@name = env[:domain_name]
@cpus = 1
@memory_size = 512*1024
# TODO get type from driver config option
@domain_type = 'kvm'
@os_type = 'hvm'
# Get path to domain image.
domain_volume = Libvirt::Util::Collection.find_matching(
env[:libvirt_compute].volumes.all, "#{@name}.img")
raise Errors::DomainVolumeExists if domain_volume == nil
@domain_volume_path = domain_volume.path
# Output the settings we're going to use to the user
env[:ui].info(I18n.t("vagrant_libvirt.creating_domain"))
env[:ui].info(" -- Name: #{@name}")
env[:ui].info(" -- Domain type: #{@domain_type}")
env[:ui].info(" -- Cpus: #{@cpus}")
env[:ui].info(" -- Memory: #{@memory_size/1024}M")
env[:ui].info(" -- Base box: #{env[:machine].box.name}")
env[:ui].info(" -- Image: #{@domain_volume_path}")
# Create libvirt domain.
# Is there a way to tell fog to create new domain with already
# existing volume? Use domain creation from template..
begin
server = env[:libvirt_compute].servers.create(
:xml => to_xml('domain'))
rescue Fog::Errors::Error => e
raise Errors::FogCreateServerError,
:error_message => e.message
end
# Immediately save the ID since it is created at this point.
env[:machine].id = server.id
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,58 @@
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
# Create a snapshot of base box image. This new snapshot is just new
# cow image with backing storage pointing to base box image. Use this
# image as new domain volume.
class CreateDomainVolume
include VagrantPlugins::Libvirt::Util::ErbTemplate
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::create_domain_volume")
@app = app
end
def call(env)
env[:ui].info(I18n.t("vagrant_libvirt.creating_domain_volume"))
# Get config options.
config = env[:machine].provider_config
# This is name of newly created image for vm.
@name = "#{env[:domain_name]}.img"
# Verify the volume doesn't exist already.
domain_volume = Libvirt::Util::Collection.find_matching(
env[:libvirt_compute].volumes.all, @name)
raise Errors::DomainVolumeExists if domain_volume
# Get path to backing image - box volume.
box_volume = Libvirt::Util::Collection.find_matching(
env[:libvirt_compute].volumes.all, env[:box_volume_name])
@backing_file = box_volume.path
# Virtual size of image. Same as box image size.
@capacity = env[:machine].box.metadata['virtual_size'] #G
# Create new volume from xml template. Fog currently doesn't support
# volume snapshots directly.
begin
domain_volume = env[:libvirt_compute].volumes.create(
:xml => to_xml('volume_snapshot'),
:pool_name => config.storage_pool_name)
rescue Fog::Errors::Error => e
raise Errors::FogDomainVolumeCreateError,
:error_message => e.message
end
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,85 @@
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
# Create network interfaces for domain, before domain is running.
class CreateNetworkInterfaces
include VagrantPlugins::Libvirt::Util::ErbTemplate
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::create_network_interfaces")
@app = app
end
def call(env)
# Get domain first.
begin
domain = env[:libvirt_compute].client.lookup_domain_by_uuid(
env[:machine].id.to_s)
rescue => e
raise Errors::NoDomainError,
:error_message => e.message
end
# Setup list of interfaces before creating them
adapters = []
# Assign main interface for provisioning to first slot.
# Use network 'default' as network for ssh connecting and
# machine provisioning. This should be maybe configurable in
# Vagrantfile in future.
adapters[0] = 'default'
env[:machine].config.vm.networks.each do |type, options|
# Other types than bridged are not supported for now.
next if type != :bridged
network_name = 'default'
network_name = options[:bridge] if options[:bridge]
if options[:adapter]
if adapters[options[:adapter]]
raise Errors::InterfaceSlotNotAvailable
end
adapters[options[:adapter].to_i] = network_name
else
empty_slot = find_empty(adapters, start=1)
raise Errors::InterfaceSlotNotAvailable if empty_slot == nil
adapters[empty_slot] = network_name
end
end
# Create each interface as new domain device
adapters.each_with_index do |network_name, slot_number|
@iface_number = slot_number
@network_name = network_name
@logger.info("Creating network interface eth#{@iface_number}")
begin
domain.attach_device(to_xml('interface'))
rescue => e
raise Errors::AttachDeviceError,
:error_message => e.message
end
end
@app.call(env)
end
private
def find_empty(array, start=0, stop=8)
for i in start..stop
return i if !array[i]
end
return nil
end
end
end
end
end

View File

@@ -0,0 +1,28 @@
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
class DestroyDomain
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::destroy_domain")
@app = app
end
def call(env)
# Destroy the server and remove the tracking ID
env[:ui].info(I18n.t("vagrant_libvirt.destroy_domain"))
domain = env[:libvirt_compute].servers.get(env[:machine].id.to_s)
domain.destroy(:destroy_volumes => true)
env[:machine].id = nil
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,121 @@
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
class HandleBoxImage
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::handle_box_image")
@app = app
end
def call(env)
# Verify box metadata for mandatory values.
#
# Virtual size has to be set for allocating space in storage pool.
box_virtual_size = env[:machine].box.metadata['virtual_size']
if box_virtual_size == nil
raise Errors::NoBoxVirtualSizeSet
end
# Support qcow2 format only for now, but other formats with backing
# store capability should be usable.
box_format = env[:machine].box.metadata['format']
if box_format == nil
raise Errors::NoBoxFormatSet
elsif box_format != 'qcow2'
raise Errors::WrongBoxFormatSet
end
# Get config options
config = env[:machine].provider_config
box_image_file = env[:machine].box.directory.join("box.img").to_s
env[:box_volume_name] = env[:machine].box.name.to_s.dup
env[:box_volume_name] << '_vagrant_box_image.img'
# Don't continue if image already exists in storage pool.
return @app.call(env) if Libvirt::Util::Collection.find_matching(
env[:libvirt_compute].volumes.all, env[:box_volume_name])
# Box is not available as a storage pool volume. Create and upload
# it as a copy of local box image.
env[:ui].info(I18n.t("vagrant_libvirt.uploading_volume"))
# Create new volume in storage pool
box_image_size = File.size(box_image_file) # B
message = "Creating volume #{env[:box_volume_name]}"
message << " in storage pool #{config.storage_pool_name}."
@logger.info(message)
begin
fog_volume = env[:libvirt_compute].volumes.create(
:name => env[:box_volume_name],
:allocation => "#{box_image_size/1024/1024}M",
:capacity => "#{box_virtual_size}G",
:format_type => box_format,
:pool_name => config.storage_pool_name)
rescue Fog::Errors::Error => e
raise Errors::FogCreateVolumeError,
:error_message => e.message
end
# Upload box image to storage pool
ret = upload_image(box_image_file, config.storage_pool_name,
env[:box_volume_name], env) do |progress|
env[:ui].clear_line
env[:ui].report_progress(progress, box_image_size, false)
end
# Clear the line one last time since the progress meter doesn't
# disappear immediately.
env[:ui].clear_line
# If upload failed or was interrupted, remove created volume from
# storage pool.
if env[:interrupted] or !ret
begin
fog_volume.destroy
rescue
nil
end
end
@app.call(env)
end
protected
# Fog libvirt currently doesn't support uploading images to storage
# pool volumes. Use ruby-libvirt client instead.
def upload_image(image_file, pool_name, volume_name, env)
image_size = File.size(image_file) # B
begin
pool = env[:libvirt_compute].client.lookup_storage_pool_by_name(
pool_name)
volume = pool.lookup_volume_by_name(volume_name)
stream = env[:libvirt_compute].client.stream
volume.upload(stream, offset=0, length=image_size)
buf_size = 1024*1024 # 1M
progress = 0
open(image_file, 'rb') do |io|
while (buff = io.read(buf_size)) do
sent = stream.send buff
progress += sent
yield progress
end
end
rescue => e
raise Errors::ImageUploadError,
:error_message => e.message
end
return true if progress == image_size
false
end
end
end
end
end

View File

@@ -0,0 +1,49 @@
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
class HandleStoragePool
include VagrantPlugins::Libvirt::Util::ErbTemplate
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::handle_storage_pool")
@app = app
end
def call(env)
# Get config options.
config = env[:machine].provider_config
# Check for storage pool, where box image should be created
fog_pool = Libvirt::Util::Collection.find_matching(
env[:libvirt_compute].pools.all, config.storage_pool_name)
return @app.call(env) if fog_pool
@logger.info("No storage pool '#{config.storage_pool_name}' is available.")
# If user specified other pool than default, don't create default
# storage pool, just write error message.
raise Errors::NoStoragePool if config.storage_pool_name != 'default'
@logger.info("Creating storage pool 'default'")
# Fog libvirt currently doesn't support creating pools. Use
# ruby-libvirt client directly.
begin
libvirt_pool = env[:libvirt_compute].client.create_storage_pool_xml(
to_xml('default_storage_pool'))
rescue => e
raise Errors::CreatingStoragePoolError,
:error_message => e.message
end
raise Errors::NoStoragePool if !libvirt_pool
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,18 @@
module VagrantPlugins
module Libvirt
module Action
# This can be used with "Call" built-in to check if the machine
# is created and branch in the middleware.
class IsCreated
def initialize(app, env)
@app = app
end
def call(env)
env[:result] = env[:machine].state.id != :not_created
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,16 @@
module VagrantPlugins
module Libvirt
module Action
class MessageAlreadyCreated
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info(I18n.t("vagrant_libvirt.already_created"))
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,16 @@
module VagrantPlugins
module Libvirt
module Action
class MessageNotCreated
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info(I18n.t("vagrant_libvirt.not_created"))
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,51 @@
require "log4r"
module VagrantPlugins
module Libvirt
module Action
# This action reads the SSH info for the machine and puts it into the
# `:machine_ssh_info` key in the environment.
class ReadSSHInfo
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant_libvirt::action::read_ssh_info")
end
def call(env)
env[:machine_ssh_info] = read_ssh_info(
env[:libvirt_compute], env[:machine])
@app.call(env)
end
def read_ssh_info(libvirt, machine)
return nil if machine.id.nil?
# Find the machine
server = libvirt.servers.get(machine.id)
if server.nil?
# The machine can't be found
@logger.info("Machine couldn't be found, assuming it got destroyed.")
machine.id = nil
return nil
end
# Get ip address of machine
ip_address = server.public_ip_address
ip_address = server.private_ip_address if ip_address == nil
return nil if ip_address == nil
# Return the info
# TODO: Some info should be configurable in Vagrantfile
return {
:host => ip_address,
:port => 22,
:username => 'root',
}
end
end
end
end
end

View File

@@ -0,0 +1,38 @@
require "log4r"
module VagrantPlugins
module Libvirt
module Action
# This action reads the state of the machine and puts it in the
# `:machine_state_id` key in the environment.
class ReadState
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant_libvirt::action::read_state")
end
def call(env)
env[:machine_state_id] = read_state(env[:libvirt_compute], env[:machine])
@app.call(env)
end
def read_state(libvirt, machine)
return :not_created if machine.id.nil?
# Find the machine
server = libvirt.servers.get(machine.id)
if server.nil? || [:"shutting-down", :terminated].include?(server.state.to_sym)
# The machine can't be found
@logger.info("Machine not found or terminated, assuming it got destroyed.")
machine.id = nil
return :not_created
end
# Return the state
return server.state.to_sym
end
end
end
end
end

View File

@@ -0,0 +1,31 @@
module VagrantPlugins
module Libvirt
module Action
# Setup name for domain and domain volumes.
class SetNameOfDomain
def initialize(app, env)
@app = app
end
def call(env)
env[:domain_name] = env[:root_path].basename.to_s.dup
env[:domain_name].gsub!(/[^-a-z0-9_]/i, "")
env[:domain_name] << "_#{Time.now.to_i}"
# Check if the domain name is not already taken
domain = Libvirt::Util::Collection.find_matching(
env[:libvirt_compute].servers.all, env[:domain_name])
if domain != nil
raise Vagrant::Errors::DomainNameExists,
:domain_name => env[:domain_name]
end
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,27 @@
require 'log4r'
module VagrantPlugins
module Libvirt
module Action
# Just start the domain.
class StartDomain
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::start_domain")
@app = app
end
def call(env)
env[:ui].info(I18n.t("vagrant_libvirt.starting_domain"))
domain = env[:libvirt_compute].servers.get(env[:machine].id.to_s)
raise Errors::NoDomainError if domain == nil
domain.start
@app.call(env)
end
end
end
end
end

View File

@@ -0,0 +1,58 @@
require "log4r"
require "vagrant/util/subprocess"
module VagrantPlugins
module Libvirt
module Action
# This middleware uses `rsync` to sync the folders over to the
# libvirt domain.
class SyncFolders
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant_libvirt::action::sync_folders")
end
def call(env)
@app.call(env)
ssh_info = env[:machine].ssh_info
env[:machine].config.vm.synced_folders.each do |id, data|
hostpath = File.expand_path(data[:hostpath], env[:root_path])
guestpath = data[:guestpath]
# Make sure there is a trailing slash on the host path to
# avoid creating an additional directory with rsync
hostpath = "#{hostpath}/" if hostpath !~ /\/$/
env[:ui].info(I18n.t("vagrant_libvirt.rsync_folder",
:hostpath => hostpath,
:guestpath => guestpath))
# Create the guest path
env[:machine].communicate.sudo("mkdir -p '#{guestpath}'")
env[:machine].communicate.sudo(
"chown #{ssh_info[:username]} '#{guestpath}'")
# Rsync over to the guest path using the SSH info
command = [
"rsync", "--verbose", "--archive", "-z",
"--exclude", ".vagrant/",
"-e", "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no -i '#{ssh_info[:private_key_path]}'",
hostpath,
"#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
r = Vagrant::Util::Subprocess.execute(*command)
if r.exit_code != 0
raise Errors::RsyncError,
:guestpath => guestpath,
:hostpath => hostpath,
:stderr => r.stderr
end
end
end
end
end
end
end

View File

@@ -0,0 +1,21 @@
require "vagrant-libvirt/util/timer"
module VagrantPlugins
module Libvirt
module Action
# This is the same as the builtin provision except it times the
# provisioner runs.
class TimedProvision < Vagrant::Action::Builtin::Provision
def run_provisioner(env, p)
timer = Util::Timer.time do
super
end
env[:metrics] ||= {}
env[:metrics]["provisioner_times"] ||= []
env[:metrics]["provisioner_times"] << [p.class.to_s, timer]
end
end
end
end
end

View File

@@ -0,0 +1,96 @@
require 'log4r'
require 'vagrant-libvirt/util/timer'
require 'vagrant/util/retryable'
module VagrantPlugins
module Libvirt
module Action
# Wait till domain is started, till it obtains an IP address and is
# accessible via ssh.
class WaitTillUp
include Vagrant::Util::Retryable
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::wait_till_up")
@app = app
end
def call(env)
# Initialize metrics if they haven't been
env[:metrics] ||= {}
# Get domain object
domain = env[:libvirt_compute].servers.get(env[:machine].id.to_s)
raise NoDomainError if domain == nil
# Wait for domain to obtain an ip address. Ip address is searched
# from arp table, either localy or remotely via ssh, if libvirt
# connection was done via ssh.
env[:ip_address] = nil
env[:metrics]["instance_ip_time"] = Util::Timer.time do
env[:ui].info(I18n.t("vagrant_libvirt.waiting_for_ip"))
retryable(:on => Fog::Errors::TimeoutError, :tries => 300) do
# If we're interrupted don't worry about waiting
next if env[:interrupted]
# Wait for domain to obtain an ip address
domain.wait_for(2) {
addresses.each_pair do |type, ip|
env[:ip_address] = ip[0]
end
env[:ip_address] != nil
}
end
end
terminate(env) if env[:interrupted]
@logger.info("Got IP address #{env[:ip_address]}")
@logger.info("Time for getting IP: #{env[:metrics]["instance_ip_time"]}")
# Machine has ip address assigned, now wait till we are able to
# connect via ssh.
env[:metrics]["instance_ssh_time"] = Util::Timer.time do
env[:ui].info(I18n.t("vagrant_libvirt.waiting_for_ssh"))
retryable(:on => Fog::Errors::TimeoutError, :tries => 60) do
# If we're interrupted don't worry about waiting
next if env[:interrupted]
# Wait till we are able to connect via ssh.
while true
# If we're interrupted then just back out
break if env[:interrupted]
break if env[:machine].communicate.ready?
sleep 2
end
end
end
terminate(env) if env[:interrupted]
@logger.info("Time for SSH ready: #{env[:metrics]["instance_ssh_time"]}")
# Booted and ready for use.
#env[:ui].info(I18n.t("vagrant_libvirt.ready"))
@app.call(env)
end
def recover(env)
return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError)
if env[:machine].provider.state.id != :not_created
# Undo the import
terminate(env)
end
end
def terminate(env)
destroy_env = env.dup
destroy_env.delete(:interrupted)
destroy_env[:config_validate] = false
destroy_env[:force_confirm_destroy] = true
env[:action_runner].run(Action.action_destroy, destroy_env)
end
end
end
end
end

View File

@@ -0,0 +1,48 @@
require 'vagrant'
module VagrantPlugins
module Libvirt
class Config < Vagrant.plugin('2', :config)
# A hypervisor name to access via Libvirt.
attr_accessor :driver
# The name of the server, where libvirtd is running.
attr_accessor :host
# If use ssh tunnel to connect to Libvirt.
attr_accessor :connect_via_ssh
# The username to access Libvirt.
attr_accessor :username
# Password for Libvirt connection.
attr_accessor :password
# Libvirt storage pool name, where box image and instance snapshots will
# be stored.
attr_accessor :storage_pool_name
def initialize
@driver = UNSET_VALUE
@host = UNSET_VALUE
@connect_via_ssh = UNSET_VALUE
@username = UNSET_VALUE
@password = UNSET_VALUE
@storage_pool_name = UNSET_VALUE
end
def finalize!
@driver = 'qemu' if @driver == UNSET_VALUE
@host = nil if @host == UNSET_VALUE
@connect_via_ssh = false if @connect_via_ssh == UNSET_VALUE
@username = nil if @username == UNSET_VALUE
@password = nil if @password == UNSET_VALUE
@storage_pool_name = 'default' if @storage_pool_name == UNSET_VALUE
end
def validate(machine)
end
end
end
end

View File

@@ -0,0 +1,90 @@
require 'vagrant'
module VagrantPlugins
module Libvirt
module Errors
class VagrantLibvirtError < Vagrant::Errors::VagrantError
error_namespace("vagrant_libvirt.errors")
end
# Storage pools and volumes exceptions
class NoStoragePool < VagrantLibvirtError
error_key(:no_storage_pool)
end
class DomainVolumeExists < VagrantLibvirtError
error_key(:domain_volume_exists)
end
class NoDomainVolume < VagrantLibvirtError
error_key(:no_domain_volume)
end
class CreatingStoragePoolError < VagrantLibvirtError
error_key(:creating_storage_pool_error)
end
class ImageUploadError < VagrantLibvirtError
error_key(:image_upload_error_error)
end
# Box exceptions
class NoBoxVolume < VagrantLibvirtError
error_key(:no_box_volume)
end
class NoBoxVirtualSizeSet < VagrantLibvirtError
error_key(:no_box_virtual_size_error)
end
class NoBoxFormatSet < VagrantLibvirtError
error_key(:no_box_format_error)
end
class WrongBoxFormatSet < VagrantLibvirtError
error_key(:wrong_box_format_error)
end
# Fog libvirt exceptions
class FogLibvirtConnectionError < VagrantLibvirtError
error_key(:fog_libvirt_connection_error)
end
class FogCreateVolumeError < VagrantLibvirtError
error_key(:fog_create_volume_error)
end
class FogCreateDomainVolumeError < VagrantLibvirtError
error_key(:fog_create_domain_volume_error)
end
class FogCreateServerError < VagrantLibvirtError
error_key(:fog_create_server_error)
end
# Other exceptions
class InterfaceSlotNotAvailable < VagrantLibvirtError
error_key(:interface_slot_not_available)
end
class RsyncError < VagrantLibvirtError
error_key(:rsync_error)
end
class DomainNameExists < VagrantLibvirtError
error_key(:domain_name_exists_error)
end
class NoDomainError < VagrantLibvirtError
error_key(:no_domain_error)
end
class AttachDeviceError < VagrantLibvirtError
error_key(:attach_device_error)
end
end
end
end

View File

@@ -0,0 +1,77 @@
begin
require 'vagrant'
rescue LoadError
raise "The Vagrant Libvirt plugin must be run within Vagrant."
end
# This is a sanity check to make sure no one is attempting to install
# this into an early Vagrant version.
if Vagrant::VERSION < '1.1.0'
raise "The Vagrant Libvirt plugin is only compatible with Vagrant 1.1+"
end
module VagrantPlugins
module Libvirt
class Plugin < Vagrant.plugin('2')
name "libvirt"
description <<-DESC
Vagrant plugin to manage VMs in libvirt.
DESC
config("libvirt", :provider) do
require_relative "config"
Config
end
provider "libvirt" do
# Setup logging and i18n
setup_logging
setup_i18n
require_relative "provider"
Provider
end
# This initializes the internationalization strings.
def self.setup_i18n
I18n.load_path << File.expand_path("locales/en.yml", Libvirt.source_root)
I18n.reload!
end
# This sets up our log level to be whatever VAGRANT_LOG is.
def self.setup_logging
require "log4r"
level = nil
begin
level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
rescue NameError
# This means that the logging constant wasn't found,
# which is fine. We just keep `level` as `nil`. But
# we tell the user.
level = nil
end
# Some constants, such as "true" resolve to booleans, so the
# above error checking doesn't catch it. This will check to make
# sure that the log level is an integer, as Log4r requires.
level = nil if !level.is_a?(Integer)
# Set the logging level on all "vagrant" namespaced
# logs as long as we have a valid level.
if level
logger = Log4r::Logger.new("vagrant_libvirt")
logger.outputters = Log4r::Outputter.stderr
logger.level = level
logger = nil
end
end
end
end
end

View File

@@ -0,0 +1,76 @@
require 'vagrant'
module VagrantPlugins
module Libvirt
# This is the base class for a provider for the V2 API. A provider
# is responsible for creating compute resources to match the
# needs of a Vagrant-configured system.
class Provider < Vagrant.plugin('2', :provider)
def initialize(machine)
@machine = machine
end
# This should return an action callable for the given name.
def action(name)
# Attempt to get the action method from the Action class if it
# exists, otherwise return nil to show that we don't support the
# given action.
action_method = "action_#{name}"
return Action.send(action_method) if Action.respond_to?(action_method)
nil
end
# This method is called if the underying machine ID changes. Providers
# can use this method to load in new data for the actual backing
# machine or to realize that the machine is now gone (the ID can
# become `nil`).
def machine_id_changed
end
# This should return a hash of information that explains how to
# SSH into the machine. If the machine is not at a point where
# SSH is even possible, then `nil` should be returned.
def ssh_info
# Run a custom action called "read_ssh_info" which does what it says
# and puts the resulting SSH info into the `:machine_ssh_info` key in
# the environment.
#
# Ssh info has following format..
#
#{
# :host => "1.2.3.4",
# :port => "22",
# :username => "mitchellh",
# :private_key_path => "/path/to/my/key"
#}
env = @machine.action("read_ssh_info")
env[:machine_ssh_info]
end
# This should return the state of the machine within this provider.
# The state must be an instance of {MachineState}.
def state
# Run a custom action we define called "read_state" which does
# what it says. It puts the state in the `:machine_state_id`
# key in the environment.
env = @machine.action("read_state")
state_id = env[:machine_state_id]
# Get the short and long description
short = I18n.t("vagrant_libvirt.states.short_#{state_id}")
long = I18n.t("vagrant_libvirt.states.long_#{state_id}")
# Return the MachineState object
Vagrant::MachineState.new(state_id, short, long)
end
def to_s
id = @machine.id.nil? ? "new" : @machine.id
"Libvirt (#{id})"
end
end
end
end

View File

@@ -0,0 +1,13 @@
<pool type='dir'>
<name>default</name>
<source>
</source>
<target>
<path>/var/lib/libvirt/images</path>
<permissions>
<mode>0755</mode>
<owner>-1</owner>
<group>-1</group>
</permissions>
</target>
</pool>

View File

@@ -0,0 +1,34 @@
<domain type='<%= @domain_type %>'>
<name><%= @name %></name>
<memory><%= @memory_size %></memory>
<vcpu><%= @cpus %></vcpu>
<os>
<type arch='x86_64'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='<%= @domain_volume_path %>'/>
<%# we need to ensure a unique target dev -%>
<target dev='vda' bus='virtio'/>
</disk>
<serial type='pty'>
<target port='0'/>
</serial>
<console type='pty'>
<target port='0'/>
</console>
<input type='mouse' bus='ps2'/>
<graphics type='vnc' port='5900' autoport='yes' listen='127.0.0.1' keymap='en-us'/>
<video>
<model type='cirrus' vram='9216' heads='1'/>
</video>
</devices>
</domain>

View File

@@ -0,0 +1,7 @@
<interface type='network'>
<source network='<%= @network_name %>'/>
<target dev='vnet<%= @iface_number %>'/>
<alias name='net<%= @iface_number %>'/>
<model type='virtio'/>
</interface>

View File

@@ -0,0 +1,26 @@
<volume>
<name><%= @name %></name>
<capacity unit="G"><%= @capacity %></capacity>
<target>
<format type='qcow2'/>
<permissions>
<owner>0</owner>
<group>0</group>
<mode>0600</mode>
<label>virt_image_t</label>
</permissions>
</target>
<backingStore>
<path><%= @backing_file %></path>
<format type='qcow2'/>
<permissions>
<owner>0</owner>
<group>0</group>
<mode>0600</mode>
<label>virt_image_t</label>
</permissions>
</backingStore>
</volume>

View File

@@ -0,0 +1,10 @@
module VagrantPlugins
module Libvirt
module Util
autoload :ErbTemplate, 'vagrant-libvirt/util/erb_template'
autoload :Collection, 'vagrant-libvirt/util/collection'
autoload :Timer, 'vagrant-libvirt/util/timer'
end
end
end

View File

@@ -0,0 +1,22 @@
module VagrantPlugins
module Libvirt
module Util
module Collection
# This method finds a matching _thing_ in a collection of
# _things_. This works matching if the ID or NAME equals to
# `name`. Or, if `name` is a regexp, a partial match is chosen
# as well.
def self.find_matching(collection, name)
collection.each do |single|
return single if single.name == name
end
nil
end
end
end
end
end

View File

@@ -0,0 +1,21 @@
require 'erb'
module VagrantPlugins
module Libvirt
module Util
module ErbTemplate
# Taken from fog source.
def to_xml template_name = nil
erb = template_name || self.class.to_s.split("::").last.downcase
path = File.join(File.dirname(__FILE__), "..", "templates",
"#{erb}.xml.erb")
template = File.read(path)
ERB.new(template, nil, '-').result(binding)
end
end
end
end
end

View File

@@ -0,0 +1,17 @@
module VagrantPlugins
module Libvirt
module Util
class Timer
# A basic utility method that times the execution of the given
# block and returns it.
def self.time
start_time = Time.now.to_f
yield
end_time = Time.now.to_f
end_time - start_time
end
end
end
end
end

View File

@@ -0,0 +1,5 @@
module VagrantPlugins
module Libvirt
VERSION = "0.0.1"
end
end