vmit 0.0.3 → 0.0.3.99

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