vmit 0.0.3 → 0.0.3.99

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,43 @@
1
+ #
2
+ # Copyright (C) 2013 Duncan Mac-Vicar P. <dmacvicar@suse.de>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'erb'
22
+ require 'abstract_method'
23
+ require 'confstruct'
24
+
25
+ module Vmit
26
+
27
+ class UnattendedInstall
28
+
29
+ attr_accessor :config
30
+ attr_accessor :location
31
+
32
+ #abstract_method :execute_autoinstall
33
+
34
+ def initialize(location)
35
+ @config = Confstruct::Configuration.new({
36
+ :packages => []
37
+ })
38
+ @location = location
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -19,7 +19,6 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  require 'rubygems'
22
- require 'open4'
23
22
  require 'progressbar'
24
23
  require 'digest/sha1'
25
24
 
@@ -46,6 +45,57 @@ module Vmit
46
45
  ("%02x"%((rand 64).to_i*4|2))+(0..4).inject(""){|s,x|s+":%02x"%(rand 256).to_i}
47
46
  end
48
47
 
48
+ def self.arch
49
+ Cheetah.run('arch', :stdout => :capture).strip
50
+ end
51
+
52
+ # @returns [Boolean] wether the port is open
53
+ # @note uses nmap
54
+ def self.port_open?(host, port)
55
+ begin
56
+ # use logger => nil until a sane way of handling
57
+ # non zero return codes is implemented in cheetah
58
+ # https://github.com/openSUSE/cheetah/pull/19
59
+ Cheetah.run(['nmap', host, '-p',
60
+ port.to_s, '-sV', '--version-all', '-oG', '-'],
61
+ ['grep', '-iq', "#{port}/open"], :logger => nil)
62
+ true
63
+ rescue Cheetah::ExecutionFailed
64
+ false
65
+ end
66
+ end
67
+
68
+ # Waits unntil that host port is open
69
+ #
70
+ # @example
71
+ # Vmit::Utils.wait_for_port('192.168.0.1', 22) do
72
+ # # do something
73
+ # end
74
+ #
75
+ #
76
+ def self.wait_for_port(host, port, &block)
77
+ chars = %w{ | / - \\ }
78
+ if block
79
+ thread = Thread.new(&block)
80
+ thread.abort_on_exception = true
81
+ end
82
+
83
+ Vmit.logger.info "Waiting for machine port #{port}..."
84
+ while true
85
+ print chars[0]
86
+
87
+ if port_open?(host, port)
88
+ Thread.kill(thread) if thread
89
+ break
90
+ end
91
+ break if thread && !thread.alive?
92
+ sleep(1)
93
+ print "\b"
94
+ chars.push chars.shift
95
+ end
96
+ puts
97
+ end
98
+
49
99
  def self.uname(bzimage)
50
100
  offset = 0
51
101
  File.open(bzimage) do |f|
@@ -19,5 +19,5 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module Vmit
22
- VERSION = "0.0.3"
22
+ VERSION = "0.0.3.99"
23
23
  end
@@ -0,0 +1,181 @@
1
+ #
2
+ # Copyright (C) 2013 Duncan Mac-Vicar P. <dmacvicar@suse.de>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'cheetah'
22
+ require 'drb'
23
+ require 'fileutils'
24
+ require 'stringio'
25
+ require 'yaml'
26
+ require 'confstruct'
27
+ # confstruct uses autoload, Deferred is
28
+ # not defined until you use Configuration
29
+ require 'confstruct/configuration'
30
+
31
+ require 'vmit/utils'
32
+
33
+ module Vmit
34
+
35
+ class Workspace
36
+
37
+ attr_accessor :work_dir
38
+
39
+ VM_GLOBAL_DEFAULTS = {
40
+ :memory => '1G',
41
+ :kernel_cmdline => [],
42
+ :virtio => true
43
+ }
44
+ SWITCH = 'br0'
45
+
46
+ def self.from_pwd
47
+ Vmit::Workspace.new(File.expand_path(Dir.pwd))
48
+ end
49
+
50
+ def name
51
+ work_dir.downcase.gsub(/[^a-z\s]/, '_')
52
+ end
53
+
54
+ # Accessor to current options
55
+ def [](key)
56
+ @config[key]
57
+ end
58
+
59
+ def config_file
60
+ File.join(work_dir, 'config.yml')
61
+ end
62
+
63
+ def initialize(work_dir)
64
+ @config = Confstruct::Configuration.new(VM_GLOBAL_DEFAULTS.merge({
65
+ :uuid => File.read("/proc/sys/kernel/random/uuid").strip,
66
+ :mac_address => Vmit::Utils.random_mac_address
67
+ }))
68
+ @work_dir = work_dir
69
+
70
+ if File.exist?(config_file)
71
+ @config.configure(YAML::load(File.open(config_file)))
72
+ end
73
+
74
+ @network = config.lookup!('network', 'default')
75
+ Vmit.logger.info "Network: #{@network}"
76
+ end
77
+
78
+ # @return [Array,<String>] sorted list of snapshots
79
+ def disk_images
80
+ Dir.glob(File.join(work_dir, '*.qcow2')).sort do |a,b|
81
+ File.ctime(a) <=> File.ctime(b)
82
+ end
83
+ end
84
+
85
+ # Takes a disk snapshot
86
+ def disk_snapshot!
87
+ disk_image_shift!
88
+ end
89
+
90
+ def disk_image_init!(opts={})
91
+ disk_image_shift!(opts)
92
+ end
93
+
94
+ DISK_INIT_DEFAULTS = {:disk_size => '10G'}
95
+
96
+ # Shifts an image, adding a new one using the
97
+ # previous newest one as backing file
98
+ #
99
+ # @param [Hash] opts options for the disk shift
100
+ # @option opts [String] :disk_size Disk size. Only used for image creation
101
+ def disk_image_shift!(opts={})
102
+ disk_config = Confstruct::Configuration.new(DISK_INIT_DEFAULTS)
103
+
104
+ file_name = File.join(work_dir, "sda-#{Time.now.to_i}.qcow2")
105
+ images = disk_images
106
+
107
+ file_name = File.join(work_dir, 'base.qcow2') if images.size == 0
108
+
109
+ args = ['/usr/bin/qemu-img', 'create',
110
+ '-f', "qcow2"]
111
+
112
+ if not images.empty?
113
+ args << '-b'
114
+ args << images.last
115
+ end
116
+
117
+ args << file_name
118
+ if images.empty?
119
+ args << disk_config.disk_size
120
+ end
121
+
122
+ Vmit.logger.info "Shifted image. Current is '#{file_name}'."
123
+ Cheetah.run(*args)
124
+ end
125
+
126
+ # Rolls back to the previous snapshot
127
+ def disk_rollback!
128
+ images = disk_images
129
+
130
+ return if images.empty?
131
+
132
+ if images.size == 1
133
+ Vmit.logger.fatal "Only the base snapshot left!"
134
+ return
135
+ end
136
+ Vmit.logger.info "Removing #{images.last}"
137
+ FileUtils.rm(images.last)
138
+ end
139
+
140
+ # @returns [String] The latest COW snapshot
141
+ def current_image
142
+ curr = disk_images.last
143
+ raise "No hard disk image available" if curr.nil?
144
+ curr
145
+ end
146
+
147
+ def options
148
+ raise 'Workspace#options is deprecated.'
149
+ end
150
+
151
+ # @return [Hash] Config of the virtual machine
152
+ # This is all options plus the defaults
153
+ def config
154
+ @config
155
+ end
156
+
157
+ # @return [Hash] config that differs from default
158
+ # and therefore relevant to be persisted in config.yml
159
+ def relevant_config
160
+ config.diff(config.default_values)
161
+ end
162
+
163
+ # Saves the configuration in config.yml
164
+ def save_config!
165
+ if not relevant_config.empty?
166
+ Vmit.logger.info "Writing config.yml..."
167
+ File.open(config_file, 'w') do |f|
168
+ f.write(relevant_config.to_yaml)
169
+ end
170
+ end
171
+ end
172
+
173
+ def to_s
174
+ config.to_s
175
+ end
176
+
177
+ BINDIR = File.join(File.dirname(__FILE__), '../../bin')
178
+
179
+ end
180
+
181
+ end
@@ -0,0 +1,5 @@
1
+ ---
2
+ memory: 2G
3
+ mac_address: 7a:7f:c7:dd:5f:bb
4
+ :sym_key: Hello
5
+
@@ -0,0 +1 @@
1
+ val1
@@ -0,0 +1 @@
1
+ 4
@@ -0,0 +1 @@
1
+ val1
@@ -0,0 +1,67 @@
1
+ #
2
+ # Copyright (C) 2013 Duncan Mac-Vicar P. <dmacvicar@suse.de>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require File.join(File.dirname(__FILE__), "helper")
22
+ require 'test/unit'
23
+ require 'vmit/install_media'
24
+ require 'tmpdir'
25
+
26
+ class InstallMedia_test < Test::Unit::TestCase
27
+
28
+ def test_scan
29
+
30
+ ['openSUSE 12.1', 'opensuse12.1', 'opensuse_12.1'].each do |key|
31
+ media = Vmit::InstallMedia.scan(key)
32
+ assert_kind_of(Vmit::SUSEInstallMedia, media)
33
+ assert_equal('http://download.opensuse.org/distribution/12.1/repo/oss/',
34
+ media.location)
35
+ end
36
+
37
+ ['openSUSE Factory', 'factory', 'opensuse_factory'].each do |key|
38
+ media = Vmit::InstallMedia.scan(key)
39
+ assert_kind_of(Vmit::SUSEInstallMedia, media)
40
+ assert_equal('http://download.opensuse.org/factory/repo/oss/',
41
+ media.location)
42
+ end
43
+
44
+ ['debian wheezy', 'Debian wheezy', 'debian wheezy'].each do |key|
45
+ media = Vmit::InstallMedia.scan(key)
46
+ assert_kind_of(Vmit::DebianInstallMedia, media)
47
+ assert_equal('http://cdn.debian.net/debian/dists/wheezy',
48
+ media.location)
49
+ end
50
+
51
+ ['sles10sp1', 'sles-10sp1', 'SLES10 SP1', 'SLE10 SP1'].each do |key|
52
+ media = Vmit::InstallMedia.scan(key)
53
+ assert_kind_of(Vmit::SUSEInstallMedia, media)
54
+ assert_equal("http://schnell.suse.de/BY_PRODUCT/sle-server-10-sp1-#{Vmit::Utils.arch}/",
55
+ media.location)
56
+ end
57
+
58
+ ['sles11', 'sles-11', 'SLES11', 'SLE11'].each do |key|
59
+ media = Vmit::InstallMedia.scan(key)
60
+ assert_kind_of(Vmit::SUSEInstallMedia, media)
61
+ assert_equal("http://schnell.suse.de/BY_PRODUCT/sle-server-11-sp0-#{Vmit::Utils.arch}/",
62
+ media.location)
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,118 @@
1
+ #
2
+ # Copyright (C) 2013 Duncan Mac-Vicar P. <dmacvicar@suse.de>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require File.join(File.dirname(__FILE__), "helper")
22
+ require 'test/unit'
23
+ require 'vmit/registry'
24
+ require 'tmpdir'
25
+
26
+ class MyTypedRegistry < Vmit::TypedRegistry
27
+ type :key1, String
28
+ type :key2, Fixnum
29
+ end
30
+
31
+ class Registry_test < Test::Unit::TestCase
32
+
33
+ def test_basic_yaml
34
+ dir = File.join(File.dirname(__FILE__), "data/registry.yml")
35
+ reg = Vmit::YamlRegistry.new(dir)
36
+
37
+ assert_equal '2G', reg[:memory]
38
+ assert_equal '7a:7f:c7:dd:5f:bb', reg[:mac_address]
39
+ assert_equal 'Hello', reg[:sym_key]
40
+ keys = reg.keys
41
+ assert_equal [], [:memory, :mac_address, :sym_key] - keys
42
+
43
+ reg.each do |k,v|
44
+ assert keys.include?(k)
45
+ assert_equal reg[k], v
46
+ end
47
+ end
48
+
49
+ def test_basic_existing_registry
50
+ dir = File.join(File.dirname(__FILE__), "data/registry")
51
+ reg = Vmit::FilesystemRegistry.new(dir)
52
+
53
+ assert_equal 'val1', reg[:key1]
54
+ assert_equal '4', reg[:key2]
55
+ keys = reg.keys
56
+ assert_equal [], [:key2, :key4, :key3, :key1] - keys
57
+
58
+ reg.each do |k,v|
59
+ assert keys.include?(k)
60
+ assert_equal reg[k], v
61
+ end
62
+ end
63
+
64
+ def test_basic_new_registry
65
+ Dir.mktmpdir do |dir|
66
+ reg = Vmit::FilesystemRegistry.new(dir)
67
+
68
+ assert_nil reg[:nonexisting_key]
69
+
70
+ reg[:hello] = "Hello"
71
+ reg[:bye] = "Bye"
72
+
73
+ assert_equal "Hello", reg[:hello]
74
+ assert_equal "Bye", reg[:bye]
75
+ end
76
+ end
77
+
78
+ def test_buffered
79
+ Dir.mktmpdir do |dir|
80
+ reg = Vmit::FilesystemRegistry.new(dir)
81
+ breg = Vmit::BufferedRegistry.new(reg)
82
+
83
+ reg[:hello] = "Hello"
84
+ reg[:bye] = "Bye"
85
+
86
+ breg[:bye] = "Bye 2"
87
+
88
+ assert_equal "Bye", reg[:bye]
89
+ assert_equal "Bye 2", breg[:bye]
90
+ breg.save!
91
+ assert_equal "Bye 2", reg[:bye]
92
+
93
+ assert_equal "Hello", breg[:hello]
94
+ reg[:hello] = "Hello 2"
95
+ assert_equal "Hello", breg[:hello]
96
+ breg.reload!
97
+ assert_equal "Hello 2", breg[:hello]
98
+ end
99
+ end
100
+
101
+ def test_typed
102
+ Dir.mktmpdir do |dir|
103
+ reg = Vmit::FilesystemRegistry.new(dir)
104
+ treg = MyTypedRegistry.new(reg)
105
+
106
+ reg[:key1] = "Hello"
107
+ reg[:key2] = "1"
108
+
109
+ assert_equal "Hello", treg[:key1]
110
+ assert_equal 1, treg[:key2]
111
+
112
+ assert_raise TypeError do
113
+ treg[:key2] = "1"
114
+ end
115
+ end
116
+ end
117
+
118
+ end