vagrant-kvm 0.1.4 → 0.1.5

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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +14 -0
  5. data/CHANGELOG.md +29 -0
  6. data/DEVELOPMENT.md +87 -0
  7. data/Gemfile +1 -0
  8. data/INSTALL.md +229 -0
  9. data/LICENSE +2 -1
  10. data/README.md +154 -63
  11. data/Rakefile +24 -1
  12. data/example_box/README.md +8 -8
  13. data/lib/vagrant-kvm/action.rb +47 -5
  14. data/lib/vagrant-kvm/action/boot.rb +0 -4
  15. data/lib/vagrant-kvm/action/clear_forwarded_ports.rb +53 -0
  16. data/lib/vagrant-kvm/action/forward_ports.rb +104 -0
  17. data/lib/vagrant-kvm/action/import.rb +97 -18
  18. data/lib/vagrant-kvm/action/init_storage_pool.rb +3 -1
  19. data/lib/vagrant-kvm/action/message_not_running.rb +16 -0
  20. data/lib/vagrant-kvm/action/network.rb +12 -13
  21. data/lib/vagrant-kvm/action/package_vagrantfile.rb +3 -1
  22. data/lib/vagrant-kvm/action/prepare_gui.rb +20 -0
  23. data/lib/vagrant-kvm/action/prepare_nfs_settings.rb +8 -16
  24. data/lib/vagrant-kvm/action/prepare_nfs_valid_ids.rb +17 -0
  25. data/lib/vagrant-kvm/action/reset_image_permission.rb +23 -0
  26. data/lib/vagrant-kvm/action/resume_network.rb +28 -0
  27. data/lib/vagrant-kvm/action/share_folders.rb +6 -5
  28. data/lib/vagrant-kvm/action/suspend.rb +8 -1
  29. data/lib/vagrant-kvm/config.rb +103 -2
  30. data/lib/vagrant-kvm/driver/driver.rb +321 -99
  31. data/lib/vagrant-kvm/errors.rb +18 -0
  32. data/lib/vagrant-kvm/provider.rb +4 -1
  33. data/lib/vagrant-kvm/util.rb +3 -0
  34. data/lib/vagrant-kvm/util/commands.rb +23 -0
  35. data/lib/vagrant-kvm/util/definition_attributes.rb +33 -0
  36. data/lib/vagrant-kvm/util/disk_info.rb +48 -0
  37. data/lib/vagrant-kvm/util/network_definition.rb +44 -84
  38. data/lib/vagrant-kvm/util/vm_definition.rb +91 -103
  39. data/lib/vagrant-kvm/version.rb +1 -1
  40. data/locales/en.yml +8 -0
  41. data/locales/ja.yml +14 -0
  42. data/spec/acceptance/vagrant-kvm_spec.rb +80 -0
  43. data/spec/fedora/10.virt.rules +10 -0
  44. data/spec/fedora/50-vagrant-libvirt-access.pkla +6 -0
  45. data/spec/spec_helper.rb +30 -0
  46. data/spec/support/libvirt_helper.rb +37 -0
  47. data/spec/support/vagrant_kvm_helper.rb +39 -0
  48. data/spec/test_files/box.xml +74 -0
  49. data/spec/vagrant-kvm/config_spec.rb +56 -0
  50. data/spec/vagrant-kvm/driver/driver_spec.rb +36 -0
  51. data/spec/vagrant-kvm/errors_spec.rb +25 -0
  52. data/spec/vagrant-kvm/util/network_definition_spec.rb +60 -0
  53. data/spec/vagrant-kvm/util/vm_definition_spec.rb +76 -0
  54. data/templates/libvirt_domain.erb +34 -12
  55. data/templates/libvirt_network.erb +13 -0
  56. data/templates/package_Vagrantfile.erb +11 -0
  57. data/vagrant-kvm.gemspec +1 -2
  58. metadata +41 -42
@@ -9,7 +9,9 @@ module VagrantPlugins
9
9
 
10
10
  def call(env)
11
11
  # Create a storage pool in tmp_path if it doesn't exist
12
- Driver::Driver.new.init_storage(env[:tmp_path])
12
+ userid = Process.uid.to_s
13
+ groupid = Process.gid.to_s
14
+ Driver::Driver.new.init_storage(env[:tmp_path], userid, groupid)
13
15
 
14
16
  @app.call(env)
15
17
  end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Action
4
+ class MessageNotRunning
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info I18n.t("vagrant.commands.common.vm_not_running")
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -15,28 +15,27 @@ module VagrantPlugins
15
15
  # TODO: Validate network configuration prior to anything below
16
16
  @env = env
17
17
 
18
- options = nil
18
+ options= {}
19
19
  env[:machine].config.vm.networks.each do |type, network_options|
20
20
  options = network_options if type == :private_network
21
21
  end
22
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 = {
23
+ options[:ip] = "192.168.123.10" unless 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
32
  :start => start_ip,
33
33
  :end => end_ip }
34
- options = {
34
+ options = {
35
35
  :base_ip => base_ip,
36
36
  :netmask => "255.255.255.0",
37
37
  :range => range
38
- }.merge(options)
39
- end
38
+ }.merge(options)
40
39
 
41
40
  hosts = []
42
41
  name = env[:machine].provider_config.name ?
@@ -19,7 +19,9 @@ module VagrantPlugins
19
19
  # box. This Vagrantfile contains the MAC address so that the user doesn't
20
20
  # have to worry about it.
21
21
  def create_vagrantfile
22
- File.open(File.join(@env["export.temp_dir"], "Vagrantfile"), "w") do |f|
22
+ tmp_dir = @env["export.temp_dir"]
23
+ tmp_dir = "/tmp" if !tmp_dir
24
+ File.open(File.join(tmp_dir, "Vagrantfile"), "w") do |f|
23
25
  f.write(KvmTemplateRenderer.render("package_Vagrantfile", {
24
26
  :base_mac => @env[:machine].provider.driver.read_mac_address
25
27
  }))
@@ -0,0 +1,20 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Action
4
+ class PrepareGui
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ config = env[:machine].provider_config
11
+ if config.gui
12
+ driver = env[:machine].provider.driver
13
+ driver.set_gui(config.vnc_port, config.vnc_autoport, config.vnc_password)
14
+ end
15
+ @app.call(env)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -9,22 +9,11 @@ module VagrantPlugins
9
9
 
10
10
  def call(env)
11
11
  @app.call(env)
12
+ @machine = env[:machine]
12
13
 
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
14
+ env[:nfs_host_ip] = read_host_ip(env[:machine])
15
+ env[:nfs_machine_ip] = read_machine_ip(env[:machine])
20
16
 
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
17
  end
29
18
 
30
19
  # Returns the IP address of the first host only network adapter
@@ -39,7 +28,8 @@ module VagrantPlugins
39
28
  return base_ip.join(".")
40
29
  end
41
30
 
42
- nil
31
+ # If no private network configuration, return default ip
32
+ "192.168.123.1"
43
33
  end
44
34
 
45
35
  # Returns the IP address of the guest by looking at the first
@@ -53,7 +43,9 @@ module VagrantPlugins
53
43
  end
54
44
  end
55
45
 
56
- nil
46
+ # XXX duplicated with network.rb default
47
+ # If no private network configuration, return default ip
48
+ "192.168.123.10"
57
49
  end
58
50
  end
59
51
  end
@@ -0,0 +1,17 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Action
4
+ class PrepareNFSValidIds
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
+ env[:nfs_valid_ids] = env[:machine].provider.driver.uuid
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Action
4
+ class ResetImagePermission
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+
11
+ #current_state = env[:machine].state.id
12
+ #if current_state == :shutoff
13
+ userid = Process.uid.to_s
14
+ groupid = Process.gid.to_s
15
+ env[:machine].provider.driver.reset_volume_permission(userid, groupid)
16
+ #end
17
+
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module VagrantPlugins
2
+ module ProviderKvm
3
+ module Action
4
+ class ResumeNetwork
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ interfaces = env[:machine].provider.driver.read_network_interfaces
11
+ interfaces.each do |nic|
12
+ next unless nic[:type] == :network
13
+
14
+ network_name = nic[:network].to_s
15
+ state = env[:machine].provider.driver.network_state?(network_name)
16
+ unless state
17
+ # start network and related services such as forward and nfs
18
+ env[:machine].provider.driver.start_network(network_name)
19
+ env[:action_runner].run(ForwardPorts, env)
20
+ end
21
+ end
22
+
23
+ @app.call(env)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -28,11 +28,12 @@ module VagrantPlugins
28
28
  # Ignore NFS shared folders
29
29
  #next if data[:nfs]
30
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
31
+ unless data[:disabled]
32
+ # convert to NFS share
33
+ data[:nfs] = true
34
+ # This to prevent overwriting the actual shared folders data
35
+ result[id] = data.dup
36
+ end
36
37
  end
37
38
  end
38
39
  end
@@ -9,7 +9,14 @@ module VagrantPlugins
9
9
  def call(env)
10
10
  if env[:machine].provider.state.id == :running
11
11
  env[:ui].info I18n.t("vagrant.actions.vm.suspend.suspending")
12
- env[:machine].provider.driver.suspend
12
+ if env[:machine].provider_config.force_suspend
13
+ env[:machine].provider.driver.suspend
14
+ elsif env[:machine].provider.driver.can_save?
15
+ env[:machine].provider.driver.save
16
+ else
17
+ env[:ui].warn ("Suspend is not supported. use pause instead.")
18
+ env[:machine].provider.driver.suspend
19
+ end
13
20
  end
14
21
 
15
22
  @app.call(env)
@@ -2,7 +2,7 @@ module VagrantPlugins
2
2
  module ProviderKvm
3
3
  class Config < Vagrant.plugin("2", :config)
4
4
  # An array of customizations to make on the VM prior to booting it.
5
- #
5
+
6
6
  # @return [Array]
7
7
  attr_reader :customizations
8
8
 
@@ -26,10 +26,61 @@ module VagrantPlugins
26
26
  # @return [String]
27
27
  attr_accessor :image_type
28
28
 
29
+ # VM image mode(clone or COW with backing file)
30
+ #
31
+ # @return [Boolean]
32
+ attr_reader :image_backing
33
+
34
+ # VM image mode(clone or COW with backing file)
35
+ #
36
+ # @return [String]
37
+ attr_accessor :image_mode
38
+
39
+ # path of qemu binary
40
+ #
41
+ # @return [String]
42
+ attr_accessor :qemu_bin
43
+
44
+ # cpu model
45
+ #
46
+ # @return [String]: x86_64/i386
47
+ attr_accessor :cpu_model
48
+
49
+ # memory size in bytes
50
+ # default: defined in box
51
+ #
52
+ # @return [String]
53
+ attr_accessor :memory_size
54
+
55
+ # core number of cpu
56
+ # default: defined in box
57
+ #
58
+ # @return [String]
59
+ attr_accessor :core_number
60
+ attr_accessor :vnc_port
61
+ attr_accessor :vnc_autoport
62
+ attr_accessor :vnc_password
63
+ attr_accessor :machine_type
64
+ attr_accessor :network_model
65
+ attr_accessor :video_model
66
+ attr_accessor :force_pause
67
+
29
68
  def initialize
30
69
  @name = UNSET_VALUE
31
70
  @gui = UNSET_VALUE
32
71
  @image_type = UNSET_VALUE
72
+ @image_mode = UNSET_VALUE
73
+ @qemu_bin = UNSET_VALUE
74
+ @cpu_model = UNSET_VALUE
75
+ @memory_size = UNSET_VALUE
76
+ @core_number = UNSET_VALUE
77
+ @vnc_port = UNSET_VALUE
78
+ @vnc_autoport = UNSET_VALUE
79
+ @vnc_password = UNSET_VALUE
80
+ @machine_type = UNSET_VALUE
81
+ @network_model = UNSET_VALUE
82
+ @video_model = UNSET_VALUE
83
+ @force_pause = UNSET_VALUE
33
84
  end
34
85
 
35
86
  # This is the hook that is called to finalize the object before it
@@ -40,7 +91,57 @@ module VagrantPlugins
40
91
  # Default is to not show a GUI
41
92
  @gui = false if @gui == UNSET_VALUE
42
93
  # Default image type is a sparsed raw
43
- @image_type = 'raw' if @image_type == UNSET_VALUE
94
+ @image_type = 'qcow2' if @image_type == UNSET_VALUE
95
+ case @image_mode
96
+ when UNSET_VALUE
97
+ @image_backing = true
98
+ when 'clone'
99
+ @image_backing = false
100
+ when 'cow'
101
+ @image_backing = true
102
+ else
103
+ @image_backing = true
104
+ end
105
+ # Search qemu binary with the default behavior
106
+ @qemu_bin = nil if @qemu_bin == UNSET_VALUE
107
+ # Default cpu model is x86_64, acceptable only x86_64/i686
108
+ @cpu_model = 'x86_64' if @cpu_model == UNSET_VALUE
109
+ @cpu_model = 'x86_64' unless @cpu_model =~ /^(i686|x86_64)$/
110
+ # Process memory size directive
111
+ # accept the case
112
+ # integer recgnized as KiB
113
+ # <num>KiB/KB/kb/MiB/MB/mb/GiB/GB/gb
114
+ #
115
+ case @memory_size
116
+ when /^([0-9][0-9]*)(KiB|kib)$/
117
+ @memory_size = ("#{$1}".to_i * 1024).to_s
118
+ when /^([0-9][0-9]*)(KB|kb)$/
119
+ @memory_size = ("#{$1}".to_i * 1000).to_s
120
+ when /^([0-9][0-9]*)(m||MiB|mib|)$/
121
+ @memory_size = ("#{$1}".to_i * 1048576).to_s
122
+ when /^([0-9][0-9]*)(MB|mb|)$/
123
+ @memory_size = ("#{$1}".to_i * 1000000).to_s
124
+ when /^([0-9][0-9]*)(g||GiB|gib)$/
125
+ @memory_size = ("#{$1}".to_i * 1073741824).to_s
126
+ when /^([0-9][0-9]*)(GB|gb|)$/
127
+ @memory_size = ("#{$1}".to_i * 1000000000).to_s
128
+ when /^([0-9][0-9]*)$/
129
+ @memory_size = ("#{$1}".to_i * 1024).to_s
130
+ when UNSET_VALUE
131
+ @memory_size = nil
132
+ else
133
+ @memory_size = nil
134
+ end
135
+ # Default core number is 1
136
+ @core_number = 1 if @core_number == UNSET_VALUE
137
+
138
+ @vnc_autoport = false if @vnc_autoport == UNSET_VALUE
139
+ @vnc_password = nil if @vnc_password == UNSET_VALUE
140
+ @vnc_port = -1 if @vnc_port == UNSET_VALUE
141
+ @machine_type = "pc-1.2" if @machine_type == UNSET_VALUE
142
+ @network_model = "virtio" if @network_model == UNSET_VALUE
143
+ @video_model = "cirrus" if @video_model == UNSET_VALUE
144
+ @force_pause = false if @force_pause == UNSET_VALUE
44
145
  end
45
146
  end
46
147
  end
@@ -11,6 +11,7 @@ module VagrantPlugins
11
11
 
12
12
  include Util
13
13
  include Errors
14
+ include Util::Commands
14
15
 
15
16
  # enum for states return by libvirt
16
17
  VM_STATE = [
@@ -32,16 +33,21 @@ module VagrantPlugins
32
33
  # XXX sufficient or have to check kvm and libvirt versions?
33
34
  attr_reader :version
34
35
 
35
- def initialize(uuid=nil)
36
+ # KVM support status
37
+ attr_reader :kvm
38
+
39
+ def initialize(uuid=nil, conn=nil)
36
40
  @logger = Log4r::Logger.new("vagrant::provider::kvm::driver")
37
41
  @uuid = uuid
38
42
  # This should be configurable
39
43
  @pool_name = "vagrant"
40
44
  @network_name = "vagrant"
41
45
 
46
+ load_kvm_module!
47
+
42
48
  # Open a connection to the qemu driver
43
49
  begin
44
- @conn = Libvirt::open('qemu:///system')
50
+ @conn = conn || Libvirt::open('qemu:///system')
45
51
  rescue Libvirt::Error => e
46
52
  if e.libvirt_code == 5
47
53
  # can't connect to hypervisor
@@ -52,9 +58,9 @@ module VagrantPlugins
52
58
  end
53
59
 
54
60
  @version = read_version
55
- if @version < "1.2.0"
61
+ if @conn.version.to_i < 1001000
56
62
  raise Errors::KvmInvalidVersion,
57
- :actual => @version, :required => "< 1.2.0"
63
+ :actual => @version, :required => ">= 1.1.0"
58
64
  end
59
65
 
60
66
  # Get storage pool if it exists
@@ -72,10 +78,73 @@ module VagrantPlugins
72
78
  end
73
79
  end
74
80
 
81
+ # create empty volume in storage pool
82
+ # args: disk_name, capacity, path, image_type, box_pool, box_path,
83
+ # backing, owner, group, mode, label
84
+ def create_volume(args={})
85
+ args = { # default values
86
+ :owner => '-1',
87
+ :group => '-1',
88
+ :mode => '0744',
89
+ :label => 'virt_image_t',
90
+ :backing => false
91
+ }.merge(args)
92
+ msg = "Creating volume #{args[:disk_name]}"
93
+ msg += " backed by volume #{args[:box_path]}" if args[:backing]
94
+ capacity = args[:capacity]
95
+ @logger.info(msg)
96
+ storage_vol_xml = <<-EOF
97
+ <volume>
98
+ <name>#{args[:disk_name]}</name>
99
+ <allocation>0</allocation>
100
+ <capacity unit="#{capacity[:unit]}">#{capacity[:size]}</capacity>
101
+ <target>
102
+ <path>#{args[:path]}</path>
103
+ <format type='#{args[:image_type]}'/>
104
+ <permissions>
105
+ <owner>#{args[:owner]}</owner>
106
+ <group>#{args[:group]}</group>
107
+ <mode>#{args[:mode]}</mode>
108
+ <label>#{args[:label]}</label>
109
+ </permissions>
110
+ </target>
111
+ EOF
112
+
113
+ if args[:backing]
114
+ storage_vol_xml += <<-EOF
115
+ <backingStore>
116
+ <path>#{args[:box_path]}</path>
117
+ <format type='#{args[:image_type]}'/>
118
+ </backingStore>
119
+ EOF
120
+ end
121
+ storage_vol_xml += "</volume>"
122
+
123
+ @logger.debug "Creating volume with XML:\n#{storage_vol_xml}"
124
+ if args[:backing]
125
+ vol = @pool.create_volume_xml(storage_vol_xml)
126
+ else
127
+ pool = @conn.lookup_storage_pool_by_name(args[:box_pool])
128
+ clonevol = pool.lookup_volume_by_path(args[:box_path])
129
+ # create_volume_xml_from() can convert disk image type automatically.
130
+ vol = @pool.create_volume_xml_from(storage_vol_xml, clonevol)
131
+ end
132
+ @pool.refresh
133
+ end
134
+
135
+ def reset_volume_permission(userid, groupid)
136
+ @logger.info("Revert image owner to #{userid}:#{groupid}")
137
+ domain = @conn.lookup_domain_by_uuid(@uuid)
138
+ definition = Util::VmDefinition.new(domain.xml_desc)
139
+ volume_path = definition.attributes[:disk]
140
+ run_root_command("chown #{userid}:#{groupid} " + volume_path)
141
+ run_root_command("chmod 660 " + volume_path)
142
+ end
143
+
75
144
  def delete
76
145
  domain = @conn.lookup_domain_by_uuid(@uuid)
77
- definition = Util::VmDefinition.new(domain.xml_desc, 'libvirt')
78
- volume = @pool.lookup_volume_by_path(definition.disk)
146
+ definition = Util::VmDefinition.new(domain.xml_desc)
147
+ volume = @pool.lookup_volume_by_path(definition.attributes[:disk])
79
148
  volume.delete
80
149
  # XXX remove pool if empty?
81
150
  @pool.refresh
@@ -84,6 +153,12 @@ module VagrantPlugins
84
153
  domain.undefine
85
154
  end
86
155
 
156
+ def find_box_disk(xml)
157
+ definition = File.open(xml) { |f|
158
+ Util::VmDefinition.new(f.read) }
159
+ definition.attributes[:disk]
160
+ end
161
+
87
162
  # Halts the virtual machine
88
163
  def halt
89
164
  domain = @conn.lookup_domain_by_uuid(@uuid)
@@ -92,63 +167,26 @@ module VagrantPlugins
92
167
 
93
168
  # Imports the VM
94
169
  #
95
- # @param [String] xml Path to the libvirt XML file.
96
- # @param [String] path Destination path for the volume.
97
- # @param [String] image_type An image type for the volume.
98
- # @return [String] UUID of the imported VM.
99
- def import(xml, path, image_type)
100
- @logger.info("Importing VM")
170
+ # @param [String] definition Path to the VM XML file.
171
+ # @param [String] volume_name Name of the imported volume
172
+ # @param [Hash] attributes
173
+ def import(definition, volume_name, args={})
174
+ @logger.info("Importing VM #{@name}")
101
175
  # create vm definition from xml
102
- definition = File.open(xml) { |f|
103
- Util::VmDefinition.new(f.read) }
104
- # copy volume to storage pool
105
- box_disk = definition.disk
106
- new_disk = File.basename(box_disk, File.extname(box_disk)) + "-" +
107
- Time.now.to_i.to_s + ".img"
108
- @logger.info("Copying volume #{box_disk} to #{new_disk}")
109
- old_path = File.join(File.dirname(xml), box_disk)
110
- new_path = File.join(path, new_disk)
111
- # we use qemu-img convert to preserve image size
112
- system("qemu-img convert -p #{old_path} -O #{image_type} #{new_path}")
113
- @pool.refresh
114
- volume = @pool.lookup_volume_by_name(new_disk)
115
- definition.disk = volume.path
116
- definition.name = @name
117
- definition.image_type = image_type
118
- # create vm
119
- @logger.info("Creating new VM")
120
- domain = @conn.define_domain_xml(definition.as_libvirt)
121
- domain.uuid
122
- end
123
-
124
- # Imports the VM from an OVF file.
125
- # XXX should be fusioned with import
126
- #
127
- # @param [String] ovf Path to the OVF file.
128
- # @param [String] path Destination path for the volume.
129
- # @param [String] image_type An image type for the volume.
130
- # @return [String] UUID of the imported VM.
131
- def import_ovf(ovf, path, image_type)
132
- @logger.info("Importing OVF definition for VM")
133
- # create vm definition from ovf
134
- definition = File.open(ovf) { |f|
135
- Util::VmDefinition.new(f.read, 'ovf') }
136
- # copy volume to storage pool
137
- box_disk = definition.disk
138
- new_disk = File.basename(box_disk, File.extname(box_disk)) + "-" +
139
- Time.now.to_i.to_s + ".img"
140
- @logger.info("Converting volume #{box_disk} to #{new_disk}")
141
- old_path = File.join(File.dirname(ovf), box_disk)
142
- new_path = File.join(path, new_disk)
143
- system("qemu-img convert -p #{old_path} -O #{image_type} #{new_path}")
144
- @pool.refresh
145
- volume = @pool.lookup_volume_by_name(new_disk)
146
- definition.disk = volume.path
147
- definition.name = @name
148
- definition.image_type = image_type
176
+ definition = File.open(definition) { |f| Util::VmDefinition.new(f.read) }
177
+ volume = @pool.lookup_volume_by_name(volume_name)
178
+ args = {
179
+ :image_type => "qcow2",
180
+ :qemu_bin => "/usr/bin/qemu",
181
+ :disk => volume.path,
182
+ :name => @name
183
+ }.merge(args)
184
+ definition.update(args)
149
185
  # create vm
150
186
  @logger.info("Creating new VM")
151
- domain = @conn.define_domain_xml(definition.as_libvirt)
187
+ xml_definition = definition.as_xml
188
+ @logger.debug("Creating new VM with XML config:\n#{xml_definition}")
189
+ domain = @conn.define_domain_xml(xml_definition)
152
190
  domain.uuid
153
191
  end
154
192
 
@@ -159,44 +197,89 @@ module VagrantPlugins
159
197
  @network = @conn.lookup_network_by_name(@network_name)
160
198
  definition = Util::NetworkDefinition.new(@network_name,
161
199
  @network.xml_desc)
162
- @network.destroy if @network.active?
163
- @network.undefine
164
200
  rescue Libvirt::RetrieveError
165
201
  # Network doesn't exist, create with defaults
166
202
  definition = Util::NetworkDefinition.new(@network_name)
167
203
  end
168
- definition.configure(config)
169
- @network = @conn.define_network_xml(definition.as_xml)
170
- @logger.info("Creating network #{@network_name}")
171
- @network.create
204
+ definition.update(config)
205
+ if @network.nil?
206
+ @logger.info("Creating network #{@network_name}")
207
+ @network = define_network(definition)
208
+ else
209
+ # Only destroy existing network if config has changed. This is
210
+ # necessary because other VM could be currently using this network
211
+ # and will loose connectivity if the network is destroyed.
212
+ old_def = Util::NetworkDefinition.new(@network_name, @network.xml_desc)
213
+ if old_def == definition && @network.active?
214
+ @logger.info "Reusing existing configuration for #{@network_name}"
215
+ else
216
+ @logger.info "Recreating network config for #{@network_name}"
217
+ @logger.debug "Old definition was:\n#{@network.xml_desc}"
218
+ @network.destroy if @network.active?
219
+ @network.undefine
220
+ @network = define_network(definition)
221
+ end
222
+ end
223
+ end
224
+
225
+ def define_network(definition)
226
+ xml = definition.as_xml
227
+ @logger.debug "Defining new network with XML:\n#{xml}"
228
+ network = @conn.define_network_xml(xml)
229
+ network.create
230
+ network
172
231
  end
173
232
 
174
233
  # Initialize or create storage pool
175
- def init_storage(base_path)
234
+ def init_storage(base_path, uid, gid)
235
+ # Storage pool doesn't exist so we create it
236
+ # create dir if it doesn't exist
237
+ # if we let libvirt create the dir it is owned by root
238
+ pool_path = base_path.join("storage-pool")
239
+ pool_path.mkpath unless Dir.exists?(pool_path)
240
+ @pool = init_storage_directory(
241
+ :pool_path => pool_path,
242
+ :pool_name => @pool_name,
243
+ :owner => uid, :group=>gid, :mode=>'755')
244
+ end
245
+
246
+ def init_storage_directory(args={})
176
247
  begin
177
248
  # Get the storage pool if it exists
178
- @pool = @conn.lookup_storage_pool_by_name(@pool_name)
179
- @logger.info("Init storage pool #{@pool_name}")
249
+ pool = @conn.lookup_storage_pool_by_name(args[:pool_name])
250
+ @logger.info("Init storage pool #{args[:pool_name]}")
180
251
  rescue Libvirt::RetrieveError
181
- # Storage pool doesn't exist so we create it
182
- # create dir if it doesn't exist
183
- # if we let libvirt create the dir it is owned by root
184
- pool_path = base_path.join("storage-pool")
185
- pool_path.mkpath unless Dir.exists?(pool_path)
186
- storage_pool_xml = <<-EOF
187
- <pool type="dir">
188
- <name>#{@pool_name}</name>
189
- <target>
190
- <path>#{pool_path}</path>
191
- </target>
192
- </pool>
193
- EOF
194
- @pool = @conn.define_storage_pool_xml(storage_pool_xml)
195
- @pool.build
196
- @logger.info("Creating storage pool #{@pool_name} in #{pool_path}")
252
+ @logger.info("Init storage pool with owner: #{args[:owner]}")
253
+ storage_pool_xml = <<-EOF
254
+ <pool type="dir">
255
+ <name>#{args[:pool_name]}</name>
256
+ <target>
257
+ <path>#{args[:pool_path]}</path>
258
+ <permissions>
259
+ <owner>#{args[:owner]}</owner>
260
+ <group>#{args[:group]}</group>
261
+ <mode>#{args[:mode]}</mode>
262
+ </permissions>
263
+ </target>
264
+ </pool>
265
+ EOF
266
+ pool = @conn.define_storage_pool_xml(storage_pool_xml)
267
+ pool.build
268
+ @logger.info("Creating storage pool #{args[:pool_name]} in #{args[:pool_path]}")
269
+ end
270
+ pool.create unless pool.active?
271
+ pool.refresh
272
+ pool
273
+ end
274
+
275
+ def free_storage_pool(pool_name)
276
+ begin
277
+ pool = @conn.lookup_storage_pool_by_name(pool_name)
278
+ pool.destroy
279
+ pool.free
280
+ rescue Libvirt::RetrieveError
281
+ @logger.info("fail to free storage pool #{pool_name}")
197
282
  end
198
- @pool.create unless @pool.active?
199
- @pool.refresh
200
283
  end
201
284
 
202
285
  # Returns a list of network interfaces of the VM.
@@ -207,12 +290,36 @@ module VagrantPlugins
207
290
  Util::VmDefinition.list_interfaces(domain.xml_desc)
208
291
  end
209
292
 
293
+ def network_state?(network_name)
294
+ begin
295
+ network = @conn.lookup_network_by_name(network_name)
296
+ network.active?
297
+ rescue Libvirt::RetrieveError
298
+ false
299
+ end
300
+ end
301
+
302
+ def start_network(network_name)
303
+ begin
304
+ network = @conn.lookup_network_by_name(network_name)
305
+ network.create unless network.active?
306
+ rescue Libvirt::RetrieveError
307
+ false
308
+ end
309
+ end
310
+
210
311
  def read_state
211
312
  domain = @conn.lookup_domain_by_uuid(@uuid)
212
313
  state, reason = domain.state
213
314
  # check if domain has been saved
214
- if VM_STATE[state] == :shutoff and domain.has_managed_save?
215
- return :saved
315
+ case VM_STATE[state]
316
+ when :shutoff
317
+ if domain.has_managed_save?
318
+ return :saved
319
+ end
320
+ return :poweroff
321
+ when :shutdown
322
+ return :poweroff
216
323
  end
217
324
  VM_STATE[state]
218
325
  end
@@ -228,6 +335,12 @@ module VagrantPlugins
228
335
  "#{maj}.#{min}.#{rel}"
229
336
  end
230
337
 
338
+ def read_mac_address
339
+ domain = @conn.lookup_domain_by_uuid(@uuid)
340
+ definition = Util::VmDefinition.new(domain.xml_desc)
341
+ definition.attributes[:mac]
342
+ end
343
+
231
344
  # Resumes the previously paused virtual machine.
232
345
  def resume
233
346
  @logger.debug("Resuming paused VM...")
@@ -241,19 +354,32 @@ module VagrantPlugins
241
354
  end
242
355
 
243
356
  def set_mac_address(mac)
244
- domain = @conn.lookup_domain_by_uuid(@uuid)
245
- definition = Util::VmDefinition.new(domain.xml_desc, 'libvirt')
246
- definition.set_mac(mac)
247
- domain.undefine
248
- @conn.define_domain_xml(definition.as_libvirt)
357
+ update_domain_xml(:mac => mac)
249
358
  end
250
359
 
251
- def set_gui
360
+ def set_gui(vnc_port, vnc_autoport, vnc_password)
361
+ @logger.debug("Enabling GUI")
362
+ update_domain_xml(
363
+ :gui => true,
364
+ :vnc_port => vnc_port,
365
+ :vnc_autoport => vnc_autoport,
366
+ :vnc_password => vnc_password)
367
+ end
368
+
369
+ def set_diskbus(disk_bus)
370
+ update_domain_xml(:disk_bus => disk_bus)
371
+ end
372
+
373
+ def update_domain_xml(options)
252
374
  domain = @conn.lookup_domain_by_uuid(@uuid)
253
- definition = Util::VmDefinition.new(domain.xml_desc, 'libvirt')
254
- definition.set_gui
375
+ # Use DOMAIN_XML_SECURE to dump ALL options (including VNC password)
376
+ original_xml = domain.xml_desc(Libvirt::Domain::DOMAIN_XML_SECURE)
377
+ definition = Util::VmDefinition.new(original_xml)
378
+ definition.update(options)
255
379
  domain.undefine
256
- @conn.define_domain_xml(definition.as_libvirt)
380
+ xml = definition.as_xml
381
+ @logger.debug("Updating domain xml\nFrom: #{original_xml}\nTo: #{xml}")
382
+ @conn.define_domain_xml(xml)
257
383
  end
258
384
 
259
385
  # Starts the virtual machine.
@@ -264,11 +390,48 @@ module VagrantPlugins
264
390
  end
265
391
 
266
392
  # Suspend the virtual machine and saves its states.
267
- def suspend
393
+ def save
268
394
  domain = @conn.lookup_domain_by_uuid(@uuid)
269
395
  domain.managed_save
270
396
  end
271
397
 
398
+ def can_save?
399
+ domain = @conn.lookup_domain_by_uuid(@uuid)
400
+ definition = Util::VmDefinition.new(domain.xml_desc)
401
+ disk_bus = definition.get(:disk_bus)
402
+ return disk_bus != 'sata'
403
+ end
404
+
405
+ # Suspend the virtual machine temporaly
406
+ def suspend
407
+ domain = @conn.lookup_domain_by_uuid(@uuid)
408
+ domain.suspend
409
+ end
410
+
411
+ # Export
412
+ def export(xml_path)
413
+ @logger.info("FIXME: export has not tested yet.")
414
+ new_disk = 'disk.img'
415
+ # create new_disk
416
+ domain = @conn.lookup_domain_by_uuid(@uuid)
417
+ definition = Util::VmDefinition.new(domain.xml_desc)
418
+ disk_image = definition.attributes[:disk]
419
+ to_path = File.dirname(xml_path)
420
+ new_path = File.join(to_path, new_disk)
421
+ @logger.info("create disk image #{new_path}")
422
+ run_command("qemu-img convert -c -S 4k -O qcow2 #{disk_image} #{new_path}")
423
+ # write out box.xml
424
+ definition.update(:disk => new_disk,:gui => false,:uuid => nil)
425
+ File.open(xml_path,'w') do |f|
426
+ f.puts(definition.as_xml)
427
+ end
428
+ # write metadata.json
429
+ json_path=File.join(to_path, 'metadata.json')
430
+ File.open(json_path,'w') do |f|
431
+ f.puts('{"provider": "kvm"}')
432
+ end
433
+ end
434
+
272
435
  # Verifies that the driver is ready and the connection is open
273
436
  #
274
437
  # This will raise a VagrantError if things are not ready.
@@ -289,6 +452,65 @@ module VagrantPlugins
289
452
  false
290
453
  end
291
454
  end
455
+
456
+ # Checks which Linux OS variants
457
+ #
458
+ # host_redhat?
459
+ # host_debian?
460
+ # host_gentoo?
461
+ # host_arch?
462
+ # @return [Boolean]
463
+ def host_redhat?
464
+ release_file = Pathname.new("/etc/redhat-release")
465
+
466
+ if release_file.exist?
467
+ release_file.open("r:ISO-8859-1:UTF-8") do |f|
468
+ contents = f.gets
469
+ return true if contents =~ /^CentOS/ # CentOS
470
+ return true if contents =~ /^Fedora/ # Fedora
471
+ return true if contents =~ /^Korora/ # Korora
472
+
473
+ # Oracle Linux < 5.3
474
+ return true if contents =~ /^Enterprise Linux Enterprise Linux/
475
+
476
+ # Red Hat Enterprise Linux and Oracle Linux >= 5.3
477
+ return true if contents =~ /^Red Hat Enterprise Linux/
478
+ end
479
+ end
480
+
481
+ false
482
+ end
483
+
484
+ def host_debian?
485
+ File.exists?("/etc/debian_version")
486
+ end
487
+
488
+ def host_gentoo?
489
+ File.exists?("/etc/gentoo-release")
490
+ end
491
+
492
+ def host_arch?
493
+ File.exist?("/etc/arch-release")
494
+ end
495
+
496
+ private
497
+ def load_kvm_module!
498
+ @logger.info("Check KVM kernel modules")
499
+ kvm = File.readlines('/proc/modules').any? { |line| line =~ /kvm_(intel|amd)/ }
500
+ unless kvm
501
+ case File.read('/proc/cpuinfo')
502
+ when /vmx/
503
+ kvm = true if run_command("sudo /sbin/modprobe kvm-intel")
504
+ when /svm/
505
+ kvm = true if run_command("sudo /sbin/modprobe kvm-amd")
506
+ else
507
+ # looks like virtualization is not supported
508
+ end
509
+ end
510
+ # FIXME: see KVM/ARM project
511
+ raise Errors::VagrantKVMError, "KVM is unavailable" unless kvm
512
+ true
513
+ end
292
514
  end
293
515
  end
294
516
  end