vagrant-kvm 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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