vagrant-kvm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +14 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +10 -0
- data/LICENSE +8 -0
- data/README.md +90 -0
- data/Rakefile +15 -0
- data/example_box/README.md +18 -0
- data/example_box/box.xml +78 -0
- data/example_box/metadata.json +3 -0
- data/lib/vagrant-kvm.rb +20 -0
- data/lib/vagrant-kvm/action.rb +268 -0
- data/lib/vagrant-kvm/action/boot.rb +22 -0
- data/lib/vagrant-kvm/action/check_box.rb +36 -0
- data/lib/vagrant-kvm/action/check_created.rb +21 -0
- data/lib/vagrant-kvm/action/check_kvm.rb +23 -0
- data/lib/vagrant-kvm/action/check_running.rb +21 -0
- data/lib/vagrant-kvm/action/created.rb +20 -0
- data/lib/vagrant-kvm/action/destroy.rb +19 -0
- data/lib/vagrant-kvm/action/destroy_confirm.rb +17 -0
- data/lib/vagrant-kvm/action/export.rb +57 -0
- data/lib/vagrant-kvm/action/forced_halt.rb +21 -0
- data/lib/vagrant-kvm/action/import.rb +54 -0
- data/lib/vagrant-kvm/action/init_storage_pool.rb +19 -0
- data/lib/vagrant-kvm/action/is_paused.rb +20 -0
- data/lib/vagrant-kvm/action/is_running.rb +20 -0
- data/lib/vagrant-kvm/action/is_saved.rb +20 -0
- data/lib/vagrant-kvm/action/match_mac_address.rb +21 -0
- data/lib/vagrant-kvm/action/message_not_created.rb +16 -0
- data/lib/vagrant-kvm/action/message_will_not_destroy.rb +17 -0
- data/lib/vagrant-kvm/action/network.rb +69 -0
- data/lib/vagrant-kvm/action/package.rb +20 -0
- data/lib/vagrant-kvm/action/package_vagrantfile.rb +31 -0
- data/lib/vagrant-kvm/action/prepare_nfs_settings.rb +61 -0
- data/lib/vagrant-kvm/action/prune_nfs_exports.rb +20 -0
- data/lib/vagrant-kvm/action/resume.rb +25 -0
- data/lib/vagrant-kvm/action/setup_package_files.rb +51 -0
- data/lib/vagrant-kvm/action/share_folders.rb +76 -0
- data/lib/vagrant-kvm/action/suspend.rb +20 -0
- data/lib/vagrant-kvm/config.rb +31 -0
- data/lib/vagrant-kvm/driver/driver.rb +271 -0
- data/lib/vagrant-kvm/errors.rb +11 -0
- data/lib/vagrant-kvm/plugin.rb +73 -0
- data/lib/vagrant-kvm/provider.rb +104 -0
- data/lib/vagrant-kvm/util.rb +12 -0
- data/lib/vagrant-kvm/util/kvm_template_renderer.rb +20 -0
- data/lib/vagrant-kvm/util/network_definition.rb +106 -0
- data/lib/vagrant-kvm/util/vm_definition.rb +192 -0
- data/lib/vagrant-kvm/version.rb +5 -0
- data/locales/en.yml +4 -0
- data/templates/libvirt_domain.erb +64 -0
- data/vagrant-kvm.gemspec +58 -0
- metadata +191 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
module Action
|
4
|
+
class MessageWillNotDestroy
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
env[:ui].info I18n.t("vagrant.commands.destroy.will_not_destroy",
|
11
|
+
:name => env[:machine].name)
|
12
|
+
@app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module ProviderKvm
|
5
|
+
module Action
|
6
|
+
# This middleware class configures networking
|
7
|
+
class Network
|
8
|
+
|
9
|
+
def initialize(app, env)
|
10
|
+
@logger = Log4r::Logger.new("vagrant::plugins::kvm::network")
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
# TODO: Validate network configuration prior to anything below
|
16
|
+
@env = env
|
17
|
+
|
18
|
+
options = nil
|
19
|
+
env[:machine].config.vm.networks.each do |type, network_options|
|
20
|
+
options = network_options if type == :private_network
|
21
|
+
end
|
22
|
+
|
23
|
+
if options.has_key?(:ip)
|
24
|
+
addr = options[:ip].split(".")
|
25
|
+
addr[3] = "1"
|
26
|
+
base_ip = addr.join(".")
|
27
|
+
addr[3] = "100"
|
28
|
+
start_ip = addr.join(".")
|
29
|
+
addr[3] = "200"
|
30
|
+
end_ip = addr.join(".")
|
31
|
+
range = {
|
32
|
+
:start => start_ip,
|
33
|
+
:end => end_ip }
|
34
|
+
options = {
|
35
|
+
:base_ip => base_ip,
|
36
|
+
:netmask => "255.255.255.0",
|
37
|
+
:range => range
|
38
|
+
}.merge(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
hosts = []
|
42
|
+
name = env[:machine].provider_config.name ?
|
43
|
+
env[:machine].provider_config.name : "default"
|
44
|
+
hosts << {
|
45
|
+
:mac => format_mac(env[:machine].config.vm.base_mac),
|
46
|
+
:name => name,
|
47
|
+
:ip => options[:ip]
|
48
|
+
}
|
49
|
+
options[:hosts] = hosts
|
50
|
+
|
51
|
+
env[:ui].info I18n.t("vagrant.actions.vm.network.preparing")
|
52
|
+
env[:machine].provider.driver.create_network(options)
|
53
|
+
|
54
|
+
@app.call(env)
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_mac(mac)
|
58
|
+
if mac.length == 12
|
59
|
+
mac = mac[0..1] + ":" + mac[2..3] + ":" +
|
60
|
+
mac[4..5] + ":" + mac[6..7] + ":" +
|
61
|
+
mac[8..9] + ":" + mac[10..11]
|
62
|
+
end
|
63
|
+
mac
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'vagrant/action/general/package'
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module ProviderKvm
|
5
|
+
module Action
|
6
|
+
class Package < Vagrant::Action::General::Package
|
7
|
+
# Doing this so that we can test that the parent is properly
|
8
|
+
# called in the unit tests.
|
9
|
+
alias_method :general_call, :call
|
10
|
+
def call(env)
|
11
|
+
# Just match up a couple environmental variables so that
|
12
|
+
# the superclass will do the right thing. Then, call the
|
13
|
+
# superclass
|
14
|
+
env["package.directory"] = env["export.temp_dir"]
|
15
|
+
general_call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
module Action
|
4
|
+
class PackageVagrantfile
|
5
|
+
|
6
|
+
include ProviderKvm::Util
|
7
|
+
|
8
|
+
def initialize(app, env)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
@env = env
|
14
|
+
create_vagrantfile
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
|
18
|
+
# This method creates the auto-generated Vagrantfile at the root of the
|
19
|
+
# box. This Vagrantfile contains the MAC address so that the user doesn't
|
20
|
+
# have to worry about it.
|
21
|
+
def create_vagrantfile
|
22
|
+
File.open(File.join(@env["export.temp_dir"], "Vagrantfile"), "w") do |f|
|
23
|
+
f.write(KvmTemplateRenderer.render("package_Vagrantfile", {
|
24
|
+
:base_mac => @env[:machine].provider.driver.read_mac_address
|
25
|
+
}))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
module Action
|
4
|
+
class PrepareNFSSettings
|
5
|
+
def initialize(app,env)
|
6
|
+
@app = app
|
7
|
+
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
@app.call(env)
|
12
|
+
|
13
|
+
using_nfs = false
|
14
|
+
env[:machine].config.vm.synced_folders.each do |id, opts|
|
15
|
+
if opts[:nfs]
|
16
|
+
using_nfs = true
|
17
|
+
break
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if using_nfs
|
22
|
+
@logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP")
|
23
|
+
env[:nfs_host_ip] = read_host_ip(env[:machine])
|
24
|
+
env[:nfs_machine_ip] = read_machine_ip(env[:machine])
|
25
|
+
|
26
|
+
raise Vagrant::Errors::NFSNoHostonlyNetwork if !env[:nfs_machine_ip]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the IP address of the first host only network adapter
|
31
|
+
#
|
32
|
+
# @param [Machine] machine
|
33
|
+
# @return [String]
|
34
|
+
def read_host_ip(machine)
|
35
|
+
ip = read_machine_ip(machine)
|
36
|
+
if ip
|
37
|
+
base_ip = ip.split(".")
|
38
|
+
base_ip[3] = "1"
|
39
|
+
return base_ip.join(".")
|
40
|
+
end
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the IP address of the guest by looking at the first
|
46
|
+
# enabled host only network.
|
47
|
+
#
|
48
|
+
# @return [String]
|
49
|
+
def read_machine_ip(machine)
|
50
|
+
machine.config.vm.networks.each do |type, options|
|
51
|
+
if type == :private_network && options[:ip].is_a?(String)
|
52
|
+
return options[:ip]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
module Action
|
4
|
+
class PruneNFSExports
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if env[:host]
|
11
|
+
uuid = env[:machine].provider.driver.uuid
|
12
|
+
env[:host].nfs_prune(uuid)
|
13
|
+
end
|
14
|
+
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
module Action
|
4
|
+
class Resume
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
current_state = env[:machine].provider.state.id
|
11
|
+
|
12
|
+
if current_state == :paused
|
13
|
+
env[:ui].info I18n.t("vagrant.actions.vm.resume.unpausing")
|
14
|
+
env[:machine].provider.driver.resume
|
15
|
+
elsif current_state == :saved
|
16
|
+
env[:ui].info I18n.t("vagrant.actions.vm.resume.resuming")
|
17
|
+
env[:action_runner].run(Boot, env)
|
18
|
+
end
|
19
|
+
|
20
|
+
@app.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
module Action
|
4
|
+
class SetupPackageFiles
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
|
8
|
+
env["package.include"] ||= []
|
9
|
+
env["package.vagrantfile"] ||= nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
files = {}
|
14
|
+
env["package.include"].each do |file|
|
15
|
+
source = Pathname.new(file)
|
16
|
+
dest = nil
|
17
|
+
|
18
|
+
# If the source is relative then we add the file as-is to the include
|
19
|
+
# directory. Otherwise, we copy only the file into the root of the
|
20
|
+
# include directory. Kind of strange, but seems to match what people
|
21
|
+
# expect based on history.
|
22
|
+
if source.relative?
|
23
|
+
dest = source
|
24
|
+
else
|
25
|
+
dest = source.basename
|
26
|
+
end
|
27
|
+
|
28
|
+
# Assign the mapping
|
29
|
+
files[file] = dest
|
30
|
+
end
|
31
|
+
|
32
|
+
if env["package.vagrantfile"]
|
33
|
+
# Vagrantfiles are treated special and mapped to a specific file
|
34
|
+
files[env["package.vagrantfile"]] = "_Vagrantfile"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Verify the mapping
|
38
|
+
files.each do |from, _|
|
39
|
+
raise Vagrant::Errors::PackageIncludeMissing,
|
40
|
+
:file => from if !File.exist?(from)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Save the mapping
|
44
|
+
env["package.files"] = files
|
45
|
+
|
46
|
+
@app.call(env)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
require "log4r"
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module ProviderKvm
|
7
|
+
module Action
|
8
|
+
class ShareFolders
|
9
|
+
def initialize(app, env)
|
10
|
+
@logger = Log4r::Logger.new("vagrant::action::vm::share_folders")
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
@env = env
|
16
|
+
|
17
|
+
prepare_folders
|
18
|
+
create_metadata
|
19
|
+
|
20
|
+
@app.call(env)
|
21
|
+
end
|
22
|
+
|
23
|
+
# This method returns an actual list of shared
|
24
|
+
# folders to create and their proper path.
|
25
|
+
def shared_folders
|
26
|
+
{}.tap do |result|
|
27
|
+
@env[:machine].config.vm.synced_folders.each do |id, data|
|
28
|
+
# Ignore NFS shared folders
|
29
|
+
#next if data[:nfs]
|
30
|
+
|
31
|
+
# convert to NFS share
|
32
|
+
data[:nfs] = true
|
33
|
+
|
34
|
+
# This to prevent overwriting the actual shared folders data
|
35
|
+
result[id] = data.dup
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Prepares the shared folders by verifying they exist and creating them
|
41
|
+
# if they don't.
|
42
|
+
def prepare_folders
|
43
|
+
shared_folders.each do |id, options|
|
44
|
+
hostpath = Pathname.new(options[:hostpath]).expand_path(@env[:root_path])
|
45
|
+
|
46
|
+
if !hostpath.directory? && options[:create]
|
47
|
+
# Host path doesn't exist, so let's create it.
|
48
|
+
@logger.debug("Host path doesn't exist, creating: #{hostpath}")
|
49
|
+
|
50
|
+
begin
|
51
|
+
hostpath.mkpath
|
52
|
+
rescue Errno::EACCES
|
53
|
+
raise Vagrant::Errors::SharedFolderCreateFailed,
|
54
|
+
:path => hostpath.to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_metadata
|
61
|
+
@env[:ui].info I18n.t("vagrant.actions.vm.share_folders.creating")
|
62
|
+
|
63
|
+
folders = []
|
64
|
+
shared_folders.each do |id, data|
|
65
|
+
folders << {
|
66
|
+
:name => id,
|
67
|
+
:hostpath => File.expand_path(data[:hostpath], @env[:root_path]),
|
68
|
+
:transient => data[:transient]
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
module Action
|
4
|
+
class Suspend
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if env[:machine].provider.state.id == :running
|
11
|
+
env[:ui].info I18n.t("vagrant.actions.vm.suspend.suspending")
|
12
|
+
env[:machine].provider.driver.suspend
|
13
|
+
end
|
14
|
+
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module ProviderKvm
|
3
|
+
class Config < Vagrant.plugin("2", :config)
|
4
|
+
# An array of customizations to make on the VM prior to booting it.
|
5
|
+
#
|
6
|
+
# @return [Array]
|
7
|
+
attr_reader :customizations
|
8
|
+
|
9
|
+
# This should be set to the name of the VM
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
attr_accessor :name
|
13
|
+
|
14
|
+
# The defined network adapters.
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
attr_reader :network_adapters
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@name = UNSET_VALUE
|
21
|
+
end
|
22
|
+
|
23
|
+
# This is the hook that is called to finalize the object before it
|
24
|
+
# is put into use.
|
25
|
+
def finalize!
|
26
|
+
# The default name is just nothing, and we default it
|
27
|
+
@name = nil if @name == UNSET_VALUE
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'libvirt'
|
2
|
+
require 'log4r'
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module ProviderKvm
|
6
|
+
module Driver
|
7
|
+
class Driver
|
8
|
+
# This is raised if the VM is not found when initializing
|
9
|
+
# a driver with a UUID.
|
10
|
+
class VMNotFound < StandardError; end
|
11
|
+
|
12
|
+
include Util
|
13
|
+
|
14
|
+
# enum for states return by libvirt
|
15
|
+
VM_STATE = [
|
16
|
+
:no_state,
|
17
|
+
:running,
|
18
|
+
:blocked,
|
19
|
+
:paused,
|
20
|
+
:shutdown,
|
21
|
+
:shutoff,
|
22
|
+
:crashed]
|
23
|
+
|
24
|
+
# The UUID of the virtual machine we represent
|
25
|
+
attr_reader :uuid
|
26
|
+
|
27
|
+
# The QEMU version
|
28
|
+
# XXX sufficient or have to check kvm and libvirt versions?
|
29
|
+
attr_reader :version
|
30
|
+
|
31
|
+
def initialize(uuid=nil)
|
32
|
+
@logger = Log4r::Logger.new("vagrant::provider::kvm::driver")
|
33
|
+
@uuid = uuid
|
34
|
+
# This should be configurable
|
35
|
+
@pool_name = "vagrant"
|
36
|
+
@network_name = "vagrant"
|
37
|
+
|
38
|
+
# Open a connection to the qemu driver
|
39
|
+
begin
|
40
|
+
@conn = Libvirt::open('qemu:///system')
|
41
|
+
rescue Libvirt::Error => e
|
42
|
+
if e.libvirt_code == 5
|
43
|
+
# can't connect to hypervisor
|
44
|
+
raise Vagrant::Errors::KvmNoConnection
|
45
|
+
else
|
46
|
+
raise e
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@version = read_version
|
50
|
+
if (@version[:maj] == 1 && @version[:min] < 2) || @version[:maj] < 1
|
51
|
+
raise Vagrant::Errors::KvmInvalidVersion
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get storage pool if it exists
|
55
|
+
begin
|
56
|
+
@pool = @conn.lookup_storage_pool_by_name(@pool_name)
|
57
|
+
@logger.info("Init storage pool #{@pool_name}")
|
58
|
+
rescue Libvirt::RetrieveError
|
59
|
+
# storage pool doesn't exist yet
|
60
|
+
end
|
61
|
+
|
62
|
+
if @uuid
|
63
|
+
# Verify the VM exists, and if it doesn't, then don't worry
|
64
|
+
# about it (mark the UUID as nil)
|
65
|
+
raise VMNotFound if !vm_exists?(@uuid)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete
|
70
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
71
|
+
definition = Util::VmDefinition.new(domain.xml_desc, 'libvirt')
|
72
|
+
volume = @pool.lookup_volume_by_path(definition.disk)
|
73
|
+
volume.delete
|
74
|
+
# XXX remove pool if empty?
|
75
|
+
@pool.refresh
|
76
|
+
# remove any saved state
|
77
|
+
domain.managed_save_remove if domain.has_managed_save?
|
78
|
+
domain.undefine
|
79
|
+
end
|
80
|
+
|
81
|
+
# Halts the virtual machine
|
82
|
+
def halt
|
83
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
84
|
+
domain.destroy
|
85
|
+
end
|
86
|
+
|
87
|
+
# Imports the VM
|
88
|
+
#
|
89
|
+
# @param [String] xml Path to the libvirt XML file.
|
90
|
+
# @param [String] path Destination path for the volume.
|
91
|
+
# @return [String] UUID of the imported VM.
|
92
|
+
def import(xml, path)
|
93
|
+
@logger.info("Importing VM")
|
94
|
+
# create vm definition from xml
|
95
|
+
definition = File.open(xml) { |f|
|
96
|
+
Util::VmDefinition.new(f.read) }
|
97
|
+
# copy volume to storage pool
|
98
|
+
box_disk = definition.disk
|
99
|
+
new_disk = File.basename(box_disk, File.extname(box_disk)) + "-" +
|
100
|
+
Time.now.to_i.to_s + ".img"
|
101
|
+
@logger.info("Copying volume #{box_disk} to #{new_disk}")
|
102
|
+
old_path = File.join(File.dirname(xml), box_disk)
|
103
|
+
new_path = File.join(path, new_disk)
|
104
|
+
# we use qemu-img convert to preserve image size
|
105
|
+
system("qemu-img convert -p #{old_path} -O raw #{new_path}")
|
106
|
+
@pool.refresh
|
107
|
+
volume = @pool.lookup_volume_by_name(new_disk)
|
108
|
+
definition.disk = volume.path
|
109
|
+
# create vm
|
110
|
+
@logger.info("Creating new VM")
|
111
|
+
domain = @conn.define_domain_xml(definition.as_libvirt)
|
112
|
+
domain.uuid
|
113
|
+
end
|
114
|
+
|
115
|
+
# Imports the VM from an OVF file.
|
116
|
+
# XXX should be fusioned with import
|
117
|
+
#
|
118
|
+
# @param [String] ovf Path to the OVF file.
|
119
|
+
# @param [String] path Destination path for the volume.
|
120
|
+
# @return [String] UUID of the imported VM.
|
121
|
+
def import_ovf(ovf, path)
|
122
|
+
@logger.info("Importing OVF definition for VM")
|
123
|
+
# create vm definition from ovf
|
124
|
+
definition = File.open(ovf) { |f|
|
125
|
+
Util::VmDefinition.new(f.read, 'ovf') }
|
126
|
+
# copy volume to storage pool
|
127
|
+
box_disk = definition.disk
|
128
|
+
new_disk = File.basename(box_disk, File.extname(box_disk)) + "-" +
|
129
|
+
Time.now.to_i.to_s + ".img"
|
130
|
+
@logger.info("Converting volume #{box_disk} to #{new_disk}")
|
131
|
+
old_path = File.join(File.dirname(ovf), box_disk)
|
132
|
+
new_path = File.join(path, new_disk)
|
133
|
+
system("qemu-img convert -p #{old_path} -O raw #{new_path}")
|
134
|
+
@pool.refresh
|
135
|
+
volume = @pool.lookup_volume_by_name(new_disk)
|
136
|
+
definition.disk = volume.path
|
137
|
+
# create vm
|
138
|
+
@logger.info("Creating new VM")
|
139
|
+
domain = @conn.define_domain_xml(definition.as_libvirt)
|
140
|
+
domain.uuid
|
141
|
+
end
|
142
|
+
|
143
|
+
# Create network
|
144
|
+
def create_network(config)
|
145
|
+
begin
|
146
|
+
# Get the network if it exists
|
147
|
+
@network = @conn.lookup_network_by_name(@network_name)
|
148
|
+
definition = Util::NetworkDefinition.new(@network_name,
|
149
|
+
@network.xml_desc)
|
150
|
+
@network.destroy if @network.active?
|
151
|
+
@network.undefine
|
152
|
+
rescue Libvirt::RetrieveError
|
153
|
+
# Network doesn't exist, create with defaults
|
154
|
+
definition = Util::NetworkDefinition.new(@network_name)
|
155
|
+
end
|
156
|
+
definition.configure(config)
|
157
|
+
@network = @conn.define_network_xml(definition.as_xml)
|
158
|
+
@logger.info("Creating network #{@network_name}")
|
159
|
+
@network.create
|
160
|
+
end
|
161
|
+
|
162
|
+
# Initialize or create storage pool
|
163
|
+
def init_storage(base_path)
|
164
|
+
begin
|
165
|
+
# Get the storage pool if it exists
|
166
|
+
@pool = @conn.lookup_storage_pool_by_name(@pool_name)
|
167
|
+
@logger.info("Init storage pool #{@pool_name}")
|
168
|
+
rescue Libvirt::RetrieveError
|
169
|
+
# Storage pool doesn't exist so we create it
|
170
|
+
# create dir if it doesn't exist
|
171
|
+
# if we let libvirt create the dir it is owned by root
|
172
|
+
pool_path = base_path.join("storage-pool")
|
173
|
+
pool_path.mkpath unless Dir.exists?(pool_path)
|
174
|
+
storage_pool_xml = <<-EOF
|
175
|
+
<pool type="dir">
|
176
|
+
<name>#{@pool_name}</name>
|
177
|
+
<target>
|
178
|
+
<path>#{pool_path}</path>
|
179
|
+
</target>
|
180
|
+
</pool>
|
181
|
+
EOF
|
182
|
+
@pool = @conn.define_storage_pool_xml(storage_pool_xml)
|
183
|
+
@pool.build
|
184
|
+
@logger.info("Creating storage pool #{@pool_name} in #{pool_path}")
|
185
|
+
end
|
186
|
+
@pool.create unless @pool.active?
|
187
|
+
@pool.refresh
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns a list of network interfaces of the VM.
|
191
|
+
#
|
192
|
+
# @return [Hash]
|
193
|
+
def read_network_interfaces
|
194
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
195
|
+
Util::VmDefinition.list_interfaces(domain.xml_desc)
|
196
|
+
end
|
197
|
+
|
198
|
+
def read_state
|
199
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
200
|
+
state, reason = domain.state
|
201
|
+
# check if domain has been saved
|
202
|
+
if VM_STATE[state] == :shutoff and domain.has_managed_save?
|
203
|
+
return :saved
|
204
|
+
end
|
205
|
+
VM_STATE[state]
|
206
|
+
end
|
207
|
+
|
208
|
+
# Return the qemu version
|
209
|
+
#
|
210
|
+
# @return [Hash] with :maj and :min version numbers
|
211
|
+
def read_version
|
212
|
+
# libvirt returns a number like 1002002 for version 1.2.2
|
213
|
+
# we return just the major.minor part like this 1002
|
214
|
+
maj = @conn.version / 1000000
|
215
|
+
min = (@conn.version - maj*1000000) / 1000
|
216
|
+
{ :maj => maj, :min => min }
|
217
|
+
end
|
218
|
+
|
219
|
+
# Resumes the previously paused virtual machine.
|
220
|
+
def resume
|
221
|
+
@logger.debug("Resuming paused VM...")
|
222
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
223
|
+
domain.resume
|
224
|
+
true
|
225
|
+
end
|
226
|
+
|
227
|
+
def set_mac_address(mac)
|
228
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
229
|
+
definition = Util::VmDefinition.new(domain.xml_desc, 'libvirt')
|
230
|
+
definition.set_mac(mac)
|
231
|
+
domain.undefine
|
232
|
+
@conn.define_domain_xml(definition.as_libvirt)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Starts the virtual machine.
|
236
|
+
def start
|
237
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
238
|
+
domain.create
|
239
|
+
true
|
240
|
+
end
|
241
|
+
|
242
|
+
# Suspend the virtual machine and saves its states.
|
243
|
+
def suspend
|
244
|
+
domain = @conn.lookup_domain_by_uuid(@uuid)
|
245
|
+
domain.managed_save
|
246
|
+
end
|
247
|
+
|
248
|
+
# Verifies that the driver is ready and the connection is open
|
249
|
+
#
|
250
|
+
# This will raise a VagrantError if things are not ready.
|
251
|
+
def verify!
|
252
|
+
if @conn.closed?
|
253
|
+
raise Vagrant::Errors::KvmNoConnection
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Checks if a VM with the given UUID exists.
|
258
|
+
#
|
259
|
+
# @return [Boolean]
|
260
|
+
def vm_exists?(uuid)
|
261
|
+
begin
|
262
|
+
@logger.info("Check if VM #{uuid} exists")
|
263
|
+
@conn.lookup_domain_by_uuid(uuid)
|
264
|
+
rescue Libvirt::RetrieveError
|
265
|
+
false
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|