vagrant-zfs-box 0.0.1

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