vagrant-zfs-box 0.0.1

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.
@@ -0,0 +1,22 @@
1
+ require 'fileutils'
2
+
3
+ module Vagrant
4
+ module Downloaders
5
+ # "Downloads" a file to a temporary file. Basically, this downloader
6
+ # simply does a file copy.
7
+ class File < Base
8
+ def self.match?(uri)
9
+ ::File.file?(::File.expand_path(uri))
10
+ end
11
+
12
+ def prepare(source_url)
13
+ raise Errors::DownloaderFileDoesntExist if !::File.file?(::File.expand_path(source_url))
14
+ end
15
+
16
+ def download!(source_url, destination_file)
17
+ @ui.info I18n.t("vagrant.downloaders.file.download")
18
+ FileUtils.symlink(::File.expand_path(source_url), destination_file.path, :force => true)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ begin
2
+ require 'vagrant_zfs'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'vagrant_zfs'
6
+ end
@@ -0,0 +1,14 @@
1
+ require 'vagrant'
2
+ require 'vagrant/action/builder'
3
+ require 'vagrant_zfs/zfs_config'
4
+ require 'vagrant_zfs/zfs'
5
+ require 'vagrant_zfs/vboxmanage'
6
+ require 'vagrant_zfs/actions'
7
+ require 'vagrant_zfs/version'
8
+
9
+ Vagrant.config_keys.register(:zfs) { ZfsConfig }
10
+ Vagrant.actions[:box_remove].replace(Vagrant::Action::Box::Destroy, VagrantZFS::Action::Box::Destroy)
11
+ Vagrant.actions[:box_add].replace(Vagrant::Action::Box::Unpackage, VagrantZFS::Action::Box::Unpackage)
12
+ Vagrant.actions[:up].delete(Vagrant::Action::VM::DefaultName)
13
+ Vagrant.actions[:up].replace(Vagrant::Action::VM::Import, VagrantZFS::Action::VM::Import)
14
+ Vagrant.actions[:destroy].replace(Vagrant::Action::VM::Destroy, VagrantZFS::Action::VM::Destroy)
@@ -0,0 +1,4 @@
1
+ require 'vagrant_zfs/actions/box/destroy'
2
+ require 'vagrant_zfs/actions/box/unpackage'
3
+ require 'vagrant_zfs/actions/vm/destroy'
4
+ require 'vagrant_zfs/actions/vm/import'
@@ -0,0 +1,23 @@
1
+ module VagrantZFS
2
+ module Action
3
+ module Box
4
+ class Destroy
5
+ def initialize(app, env)
6
+ @app = app
7
+ @env = env
8
+ end
9
+
10
+ def call(env)
11
+ # Delete the existing box
12
+ env[:ui].info I18n.t("vagrant.actions.box.destroy.destroying", :name => env[:box_name])
13
+ VagrantZFS::ZFS.destroy_at env['box_directory'].to_s
14
+
15
+ # Reload the box collection
16
+ env[:box_collection].reload!
17
+
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,82 @@
1
+ require 'archive/tar/minitar'
2
+ module VagrantZFS
3
+ module Action
4
+ module Box
5
+ # Unpackages a downloaded box to a given directory with a given
6
+ # name.
7
+ #
8
+ # This variant will first create a ZFS directory from a configured
9
+ # zpool and then unpack the box into the directory.
10
+ #
11
+ # # Required Variables
12
+ #
13
+ # * `download.temp_path` - A location for the downloaded box. This is
14
+ # set by the {Download} action.
15
+ # * `box` - A {Vagrant::Box} object.
16
+ #
17
+ class Unpackage
18
+ attr_reader :box_directory
19
+
20
+ def initialize(app, env)
21
+ @logger = Log4r::Logger.new("vagrant_zfs::action::box::unpackage")
22
+ @app = app
23
+ @env = env
24
+ end
25
+
26
+ def call(env)
27
+ @env = env
28
+
29
+ setup_box_directory
30
+ decompress
31
+
32
+ @app.call(@env)
33
+ end
34
+
35
+ def recover(env)
36
+ if box_directory && File.directory?(box_directory)
37
+ VagrantZFS::ZFS.destroy env[:zfs_name]
38
+ FileUtils.rm_rf(box_directory)
39
+ end
40
+ end
41
+
42
+ def zpool
43
+ # Is the zpool specified in the Vagrantfile?
44
+ if @env['global_config'].zfs.zpool
45
+ @env['global_config'].zfs.zpool
46
+ else
47
+ # If we have only one zpool available, go with that.
48
+ zpools = VagrantZFS::ZFS.zpool_list
49
+ if zpools.length == 1
50
+ zpools.first
51
+ else
52
+ raise Exception, "zpool not specified and more than one available."
53
+ end
54
+ end
55
+ end
56
+
57
+ def setup_box_directory
58
+ if File.directory?(@env["box_directory"])
59
+ raise Errors::BoxAlreadyExists, :name => @env["box_name"]
60
+ end
61
+
62
+ puts "ZPOOL: #{zpool}"
63
+ @env[:zfs_name] = "#{zpool}/vagrant_#{@env["box_name"]}"
64
+ mountpoint = "#{@env["box_directory"]}"
65
+ VagrantZFS::ZFS.create @env[:zfs_name], mountpoint
66
+ @box_directory = @env["box_directory"]
67
+ end
68
+
69
+ def decompress
70
+ Dir.chdir(@env["box_directory"]) do
71
+ @env[:ui].info I18n.t("vagrant.actions.box.unpackage.extracting")
72
+ begin
73
+ Archive::Tar::Minitar.unpack(@env["download.temp_path"], @env["box_directory"].to_s)
74
+ rescue SystemCallError
75
+ raise Errors::BoxUnpackageFailure
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,38 @@
1
+ module VagrantZFS
2
+ module Action
3
+ module VM
4
+ class Destroy
5
+ def initialize(app, env)
6
+ @logger = Log4r::Logger.new("vagrant_zfs::action::vm::destroy")
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ env[:ui].info I18n.t("vagrant.actions.vm.destroy.destroying")
12
+
13
+ cmd = "VBoxManage showvminfo #{env[:vm].uuid}"
14
+ stdout, stderr, status = Open3.capture3(*cmd)
15
+ if status.success? and stderr.empty?
16
+ instance_name = stdout.lines.grep(/^Name:\s*(.+)/){$1}.first
17
+ else
18
+ raise Exception, "Could not find instance name for VM #{env[:vm].uuid}"
19
+ end
20
+
21
+ zpool = 'SSD'
22
+ fs = "#{zpool}/vagrant_#{env[:vm].config.vm.box}"
23
+ clonename = "#{fs}/#{instance_name}"
24
+ snapname = "#{fs}@#{instance_name}"
25
+
26
+ env[:vm].driver.delete
27
+ env[:vm].uuid = nil
28
+
29
+ VagrantZFS::ZFS.destroy clonename
30
+ VagrantZFS::ZFS.destroy snapname
31
+
32
+
33
+ @app.call(env)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ module VagrantZFS
2
+ module Action
3
+ module VM
4
+ class Import
5
+ def initialize(app, env)
6
+ @logger = Log4r::Logger.new("vagrant_zfs::action::vm::import")
7
+ @env = env
8
+ @app = app
9
+ end
10
+
11
+ def find_basebox_filesystem
12
+ fs = VagrantZFS::ZFS.mounts.select do |mountpoint,fs|
13
+ mountpoint == @env[:vm].box.directory.to_s
14
+ end.first[1]
15
+ @logger.debug "Found base box filesystem: #{fs}"
16
+ fs
17
+ end
18
+
19
+ def call(env)
20
+ env[:ui].info I18n.t("vagrant.actions.vm.import.importing", :name => env[:vm].box.name)
21
+
22
+ # Import the virtual machine
23
+ ovf_file = env[:vm].box.directory.join("box.ovf").to_s
24
+
25
+ fs = find_basebox_filesystem
26
+
27
+ instance_name = env[:root_path].basename.to_s + "_#{Time.now.to_i}"
28
+ env[:name] = instance_name
29
+
30
+ VagrantZFS::ZFS.snapshot fs, instance_name
31
+
32
+ user_home = ENV['HOME']
33
+ vagrant_home = "#{user_home}/.vagrant.d"
34
+ instance_root = vagrant_home + "/instances"
35
+
36
+ clonename = "#{fs}/#{instance_name}"
37
+ mountpoint = "#{instance_root}/#{instance_name}"
38
+ VagrantZFS::ZFS.clone! "#{fs}@#{instance_name}", clonename
39
+ VagrantZFS::ZFS.set_mountpoint clonename, mountpoint
40
+
41
+ hdd = instance_root + "/" + instance_name + "/box-disk1.vmdk"
42
+
43
+ env[:vm].uuid = VagrantZFS::VBoxManage.createvm instance_name, instance_root
44
+
45
+ VagrantZFS::VBoxManage.setup env[:vm].uuid, hdd
46
+
47
+ # # If we got interrupted, then the import could have been
48
+ # # interrupted and its not a big deal. Just return out.
49
+ # return if env[:interrupted]
50
+
51
+ # Flag as erroneous and return if import failed
52
+ raise Errors::VMImportFailure if !env[:vm].uuid
53
+
54
+ # # Import completed successfully. Continue the chain
55
+ @app.call(env)
56
+ end
57
+
58
+ def recover(env)
59
+ if env[:vm].created?
60
+ return if env["vagrant.error"].is_a?(Errors::VagrantError)
61
+
62
+ # Interrupted, destroy the VM. We note that we don't want to
63
+ # validate the configuration here.
64
+ destroy_env = env.clone
65
+ destroy_env[:validate] = false
66
+ env[:action_runner].run(:destroy, destroy_env)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,105 @@
1
+ require 'open3'
2
+ module VagrantZFS
3
+ class VBoxManage
4
+ def self.exec cmd
5
+ cmd = "VBoxManage " + cmd
6
+ stdout, stderr, status = Open3.capture3(cmd)
7
+
8
+ if status.success? and stderr.empty?
9
+ return stdout
10
+ else
11
+ raise Exception, "VBoxManage Error: #{cmd}\n #{stdout}\n #{stderr}\n #{status}"
12
+ end
13
+ end
14
+
15
+ def self.createvm instance_name, instance_root
16
+ cmd = "createvm --name #{instance_name} --register --basefolder #{instance_root}"
17
+ lines = self.exec(cmd).split(/\n/)
18
+ uuid = lines.grep(/^UUID:\s*([-0-9a-z]+)/){$1}.first
19
+ end
20
+
21
+ def self.modifyvm uuid, arg
22
+ self.exec "modifyvm #{uuid} #{arg}"
23
+ end
24
+
25
+ def self.add_nat uuid
26
+ self.modifyvm uuid, "--nic1 nat"
27
+ end
28
+
29
+ def self.cpus uuid, cpus
30
+ self.modifyvm uuid, "--cpus #{cpus}"
31
+ end
32
+
33
+ def self.memory uuid, memory
34
+ self.modifyvm uuid, "--memory #{memory}"
35
+ end
36
+
37
+ def self.gethduuid file
38
+ # I found the line number may be 23, but search 40 just to be sure.
39
+ cmd="head -n 40 #{file}|grep --text --byte-offset --max-count=1 ddb.uuid.image="
40
+ stdout, stderr, status = Open3.capture3(cmd)
41
+ unless status.success? and stderr.empty?
42
+ raise Exception, "gethduuid Error: #{cmd}\n #{stdout}\n #{stderr}\n #{status}"
43
+ end
44
+ # UUID format is a string of hyphen-punctuated character groups of 8-4-4-4-12.
45
+ uuid = stdout.match(/ddb.uuid.image="(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})"/).captures.first
46
+ end
47
+
48
+ def self.sethduuid_shell file
49
+ # I found the line number may be 23, but search 40 just to be sure.
50
+ cmd="head -n 40 #{file}|grep --text --byte-offset --max-count=1 ddb.uuid.image="
51
+ stdout, stderr, status = Open3.capture3(cmd)
52
+ unless status.success? and stderr.empty?
53
+ raise Exception, "gethduuid Error: #{cmd}\n #{stdout}\n #{stderr}\n #{status}"
54
+ end
55
+ # The number before : is the byte offset number.
56
+ uuid_offset = stdout.split(':').first.to_i + 'ddb.uuid.image="'.length
57
+ uuid_length = 36
58
+
59
+ # Generate a new UUID
60
+ stdout, stderr, status = Open3.capture3("uuidgen")
61
+ unless status.success? and stderr.empty?
62
+ raise Exception, "gethduuid Error: #{cmd}\n #{stdout}\n #{stderr}\n #{status}"
63
+ end
64
+ new_uuid = stdout.chomp
65
+
66
+ # Edit the vmdk-file inplace.
67
+ cmd = "echo #{new_uuid} | dd of=#{file} seek=#{uuid_offset} bs=1 count=#{uuid_length} conv=notrunc"
68
+ `#{cmd}` ? new_uuid : nil
69
+ end
70
+
71
+ def self.sethduuid file
72
+ self.exec "internalcommands sethduuid #{file}"
73
+ end
74
+
75
+ def self.add_storagectl uuid
76
+ self.exec "storagectl #{uuid} --name 'SATA Controller' --add sata --controller IntelAHCI --sataportcount 4 --hostiocache on --bootable on"
77
+ end
78
+
79
+ def self.add_disk uuid, hddfile
80
+ self.exec "storageattach #{uuid} --storagectl 'SATA Controller' --port 0 --type hdd --nonrotational on --medium #{hddfile}"
81
+ end
82
+
83
+ def self.setup uuid, hddfile
84
+ uuid_pre = self.gethduuid(hddfile)
85
+ self.sethduuid hddfile
86
+ uuid_post = self.gethduuid(hddfile)
87
+
88
+ # Did self.sethduuid work?
89
+ unless uuid_pre != uuid_post
90
+ puts "VBoxManage internalcommands sethduuid did not work. Trying shell with dd"
91
+ self.sethduuid_shell hddfile
92
+ uuid_post = self.gethduuid(hddfile)
93
+ unless uuid_pre != uuid_post
94
+ puts "Did not succeed to sethduuid."
95
+ end
96
+ end
97
+
98
+ self.add_storagectl uuid
99
+ self.add_disk uuid, hddfile
100
+ self.add_nat uuid
101
+ self.cpus uuid, "1"
102
+ self.memory uuid, "512"
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,3 @@
1
+ module VagrantZFS
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,79 @@
1
+ require 'open3'
2
+ module VagrantZFS
3
+ class ZFS
4
+ def self.exec cmd
5
+ cmd = "zfs " + cmd
6
+ stdout, stderr, status = Open3.capture3(cmd)
7
+
8
+ if status.success? and stderr.empty?
9
+ return stdout
10
+ else
11
+ raise Exception, "ZFS Error: #{cmd}\n #{stdout}\n #{stderr}\n #{status}"
12
+ end
13
+ end
14
+
15
+ def self.zpool_list
16
+ `zpool list -H -o name`.split(/\n/)
17
+ end
18
+
19
+ def self.create fs_name, mountpoint
20
+ cmd = "create -o mountpoint=#{mountpoint} #{fs_name}"
21
+ self.exec cmd
22
+ end
23
+
24
+ def self.destroy fs_name
25
+ cmd = "destroy #{fs_name}"
26
+ self.exec cmd
27
+ end
28
+
29
+ def self.destroy_at mountpoint
30
+ fs = VagrantZFS::ZFS.mounts.select do |mounted_at,fs|
31
+ mounted_at == mountpoint
32
+ end.first[1]
33
+ puts "Will destroy #{fs} mounted at #{mountpoint}"
34
+ self.destroy fs
35
+ end
36
+
37
+ def self.mounts
38
+ cmd = "get -rHp -oname,value mountpoint"
39
+ lines = self.exec(cmd).split(/\n/)
40
+ mounts = lines.collect do |line|
41
+ fs, path = line.chomp.split(/\t/, 2)
42
+ [path, fs]
43
+ end
44
+ Hash[mounts]
45
+ end
46
+
47
+ def self.set_mountpoint fs_name, mountpoint
48
+ cmd = "set mountpoint=#{mountpoint} #{fs_name}"
49
+ self.exec cmd
50
+ end
51
+
52
+ def self.snapshot(fsname, snapname)
53
+ #raise NotFound, "no such filesystem" if !exist?
54
+ #raise AlreadyExists, "Snapshot #{snapname} already exists" if ZFS("#{fsname}@#{snapname}").exist?
55
+
56
+ cmd = "snapshot #{fsname}@#{snapname}"
57
+ self.exec cmd
58
+ return "#{fsname}@#{snapname}"
59
+ end
60
+
61
+ def self.snapshot(fsname, snapname, opts={})
62
+ #raise NotFound, "no such filesystem" if !exist?
63
+ #raise AlreadyExists, "Snapshot #{snapname} already exists" if ZFS("#{fsname}@#{snapname}").exist?
64
+
65
+ cmd = "snapshot #{fsname}@#{snapname}"
66
+ self.exec cmd
67
+ return "#{fsname}@#{snapname}"
68
+ end
69
+
70
+ # Clone snapshot
71
+ def self.clone!(snapname, clonename)
72
+ #clonename = clone.name if clone.is_a? ZFS
73
+ #raise AlreadyExists if ZFS(clone).exist?
74
+
75
+ cmd = "clone #{snapname} #{clonename}"
76
+ self.exec cmd
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ class ZfsConfig < Vagrant::Config::Base
2
+ attr_accessor :zpool
3
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-zfs-box
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lars Tobias Skjong-Borsting
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-18 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ZFS plugin that uses snapshots and clones to speed up box creation for
15
+ Vagrant 1.0
16
+ email: larstobi@relatime.no
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/vagrant/downloaders/file.rb
22
+ - lib/vagrant_init.rb
23
+ - lib/vagrant_zfs/zfs.rb
24
+ - lib/vagrant_zfs/version.rb
25
+ - lib/vagrant_zfs/actions/box/destroy.rb
26
+ - lib/vagrant_zfs/actions/box/unpackage.rb
27
+ - lib/vagrant_zfs/actions/vm/destroy.rb
28
+ - lib/vagrant_zfs/actions/vm/import.rb
29
+ - lib/vagrant_zfs/zfs_config.rb
30
+ - lib/vagrant_zfs/actions.rb
31
+ - lib/vagrant_zfs/vboxmanage.rb
32
+ - lib/vagrant_zfs.rb
33
+ homepage: http://rubygems.org/gems/vagrant-zfs-box
34
+ licenses: []
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 1.8.24
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: ZFS plugin for Vagrant 1.0
57
+ test_files: []