vmit 0.0.3

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,161 @@
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 'abstract_method'
22
+ require 'cheetah'
23
+ require 'ipaddress'
24
+ require 'yaml'
25
+
26
+ require 'vmit/refcounted_resource'
27
+
28
+ module Vmit
29
+
30
+ class Network < RefcountedResource
31
+
32
+ abstract_method :connect_interface
33
+ abstract_method :disconnect_interface
34
+
35
+ def resource_class
36
+ 'network'
37
+ end
38
+
39
+ def self.create(config)
40
+ case config
41
+ when Hash then from_config(config)
42
+ when String then from_alias(config)
43
+ else raise "Can't build network from #{config}"
44
+ end
45
+ end
46
+
47
+ def self.from_alias(name)
48
+ File.open(File.join(ENV['HOME'], '.vmit', 'networks.yml')) do |f|
49
+ # transform keys into Symbols
50
+ networks = YAML::load(f)
51
+ if networks.has_key?(name)
52
+ return from_config(networks[name].symbolize_keys)
53
+ else
54
+ raise "Unknown network #{name}"
55
+ end
56
+ end
57
+ end
58
+
59
+ def self.from_config(config)
60
+ BridgedNetwork.new(config[:address])
61
+ end
62
+
63
+ def self.default
64
+ BridgedNetwork.new(BridgedNetwork::DEFAULT_NETWORK)
65
+ end
66
+ end
67
+
68
+ # Implementation of networking with a bridge with optional
69
+ # NAT to the host interface.
70
+ #
71
+ class BridgedNetwork < Network
72
+
73
+ DEFAULT_NETWORK = '192.168.58.254/24'
74
+
75
+ def initialize(address)
76
+ @address = IPAddress(address).network
77
+ brdevice = 'br0'
78
+ @brdevice = brdevice
79
+ super("#{@brdevice}-#{@address.to_u32}")
80
+ end
81
+
82
+ def to_s
83
+ "#{@brdevice}:#{@address.to_string}"
84
+ end
85
+
86
+ # reimplemented from RefcountedResource
87
+ def on_up
88
+ Vmit.logger.info "Bringing up bridged network #{@address.to_string} on #{@brdevice}"
89
+ Vmit.logger.info " `-> managed by #{lockfile_path}"
90
+ # setup bridge
91
+ # may be use 'ip', 'link', 'show', 'dev', devname to check if
92
+ # the bridge is there?
93
+ Cheetah.run '/sbin/brctl', 'addbr', @brdevice
94
+ File.write("/proc/sys/net/ipv6/conf/#{@brdevice}/disable_ipv6", 1)
95
+ File.write('/proc/sys/net/ipv4/ip_forward', 1)
96
+ Cheetah.run '/sbin/brctl', 'stp', @brdevice, 'on'
97
+ #Cheetah.run '/sbin/brctl', 'setfd', @brdevice, '0' rescue nil
98
+ # setup network and dhcp on bridge
99
+ Cheetah.run '/sbin/ifconfig', @brdevice, @address.network.hosts[0].to_s
100
+ Cheetah.run '/sbin/ifconfig', @brdevice, 'up'
101
+ Cheetah.run 'iptables', '-t', 'nat', '-A', 'POSTROUTING', '-s', @address.network.to_string,
102
+ '!', '-d', @address.network.to_string, '-j', 'MASQUERADE'
103
+
104
+ start_dnsmasq
105
+ end
106
+
107
+ def connect_interface(device)
108
+ Vmit.logger.info " Connecting #{device} --> #{@brdevice}"
109
+ #Vmit::Utils.run_command(*['ovs-vsctl', 'add-port', SWITCH, ARGV[0]])
110
+ Cheetah.run '/sbin/brctl', 'addif', @brdevice, device
111
+ end
112
+
113
+ # reimplemented from RefcountedResource
114
+ def on_down
115
+ Vmit.logger.info "Bringing down bridged network #{@address.to_string} on #{@brdevice}"
116
+ Vmit.logger.info " `-> managed by #{lockfile_path}"
117
+ Cheetah.run '/sbin/ifconfig', @brdevice, 'down'
118
+ Cheetah.run '/sbin/brctl', 'delbr', @brdevice
119
+ Cheetah.run 'iptables', '-t', 'nat', '-D', 'POSTROUTING', '-s', @address.network.to_string,
120
+ '!', '-d', @address.network.to_string, '-j', 'MASQUERADE'
121
+ kill_dnsmasq
122
+ end
123
+
124
+ # reimplemented from RefcountedResource
125
+ def disconnect_interface(device)
126
+ Vmit.logger.info " Disconnecting #{device} -X-> #{@brdevice}"
127
+ #Vmit::Utils.run_command(*['ovs-vsctl', 'del-port', SWITCH, ARGV[0]])
128
+ Cheetah.run '/sbin/brctl', 'delif', @brdevice, device
129
+ end
130
+
131
+ # reimplemented from RefcountedResource
132
+ def on_acquire
133
+ end
134
+
135
+ # reimplemented from RefcountedResource
136
+ def on_release
137
+ end
138
+
139
+ def start_dnsmasq
140
+ dnsmasq_args = %W(dnsmasq -Z -x #{dnsmasq_pidfile} --strict-order --bind-interfaces --listen-address #{@address.network.hosts[0]} --dhcp-range #{@address.network.hosts[1]},#{@address.network.hosts.last})
141
+ Vmit.logger.debug "dnsmasq arguments: '#{dnsmasq_args.join(' ')}'"
142
+ IO.popen(dnsmasq_args)
143
+ #Vmit.logger.info " dnsmasq spawned with pid #{dnsmasq_pid}"
144
+ end
145
+
146
+ def kill_dnsmasq
147
+ Vmit.logger.info "Killing dnsmasq (#{dnsmasq_pid})"
148
+ Process.kill('SIGTERM', dnsmasq_pid)
149
+ end
150
+
151
+ def dnsmasq_pid
152
+ File.read(dnsmasq_pidfile).strip.to_i
153
+ end
154
+
155
+ def dnsmasq_pidfile
156
+ File.join(lockfile_dir, 'dnsmasq.pid')
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,82 @@
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 'vmit/autoyast'
22
+ require 'vmit/kickstart'
23
+ require 'abstract_method'
24
+ require 'clamp'
25
+ require 'net/http'
26
+ require 'tmpdir'
27
+ require 'uri'
28
+ require 'vmit'
29
+
30
+ module Vmit
31
+ module Plugins
32
+
33
+ # Bootstrap allows to initialize a virtual machine
34
+ # from (currently) a (SUSE) repository.
35
+ #
36
+ # It will perform an autoinstallation based on
37
+ # the repository.
38
+ #
39
+ class Bootstrap < ::Clamp::Command
40
+
41
+ option ["-s","--disk-size"], "SIZE",
42
+ "Initialize disk with SIZE (eg: 10M, 10G, 10K)" do |disk_size|
43
+ if not disk_size =~ /(\d)+(M|K|G)/
44
+ raise ArgumentError, "Disk size should be given as 1M, 2G, etc"
45
+ end
46
+ disk_size
47
+ end
48
+
49
+ parameter "LOCATION", "Repository URL or ISO image to bootstrap from"
50
+
51
+ def execute
52
+ Vmit.logger.info 'Starting bootstrap'
53
+ curr_dir = File.expand_path(Dir.pwd)
54
+ vm = Vmit::VirtualMachine.new(curr_dir)
55
+
56
+ Vmit.logger.info ' Deleting old images'
57
+ FileUtils.rm_f(Dir.glob('*.qcow2'))
58
+ opts = {}
59
+ opts[:disk_size] = disk_size if disk_size
60
+ vm.disk_image_init!(opts)
61
+ vm.save_config!
62
+
63
+ uri = URI.parse(location)
64
+ bootstrap = [Vmit::Bootstrap::FromImage,
65
+ Vmit::Bootstrap::FromMedia].find do |method|
66
+ method.accept?(uri)
67
+ end
68
+
69
+ if bootstrap
70
+ bootstrap.new(vm, uri).execute
71
+ else
72
+ raise "Can't bootstrap from #{location}"
73
+ end
74
+
75
+ Vmit.logger.info 'Creating snapshot of fresh system.'
76
+ vm.disk_snapshot!
77
+ Vmit.logger.info 'Bootstraping done. Call vmit run to start your system.'
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,33 @@
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 'clamp'
22
+
23
+ module Vmit
24
+ module Plugins
25
+ class Hello < ::Clamp::Command
26
+
27
+ def execute
28
+ puts "Hello"
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,134 @@
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 'abstract_method'
22
+ require 'tmpdir'
23
+
24
+ module Vmit
25
+
26
+ # This class allows to bring a resource represented
27
+ # by a shared lock file only once by the
28
+ # the first process using it, and down when the
29
+ # last process finishes.
30
+ #
31
+ # Think of it as the first one that enters the room
32
+ # turn the lights on, and the last one that exits,
33
+ # turns the lights off.
34
+ #
35
+ # Call:
36
+ #
37
+ # on_up : will bring the resource up if needed
38
+ # on_down: will bring tthe resource down if no more users
39
+ # on_acquire: will start using the resource
40
+ # on_release: will stop using the resource
41
+ #
42
+ # Then use the class like this:
43
+ #
44
+ # YourRefCountedResource.auto('name') do
45
+ # # do something
46
+ # end
47
+ #
48
+ class RefcountedResource
49
+
50
+ attr_reader :name
51
+ attr_reader :lockfile_path
52
+ attr_reader :lockfile_dir
53
+
54
+ # The resource class. Resources with the
55
+ # same class and name are considered to be
56
+ # the same resource by the locking and refcounting
57
+ # mechanism.
58
+ #
59
+ # For example, you may subclass RefcountedResource as
60
+ # Network, and then have multiple Network subclasses, but
61
+ # you can reimplement resource_class once in Network so that
62
+ # all Network subclasses have the same resource class.
63
+ #
64
+ def resource_class
65
+ (self.class.name.split('::').last || '').downcase
66
+ end
67
+
68
+ def initialize(name)
69
+ @name = name
70
+ # Allow the testcases to run as not root
71
+ resource_dir = File.join(Vmit::RUN_DIR, 'resources')
72
+ @lockfile_dir = File.join(resource_dir, resource_class, name)
73
+ FileUtils.mkdir_p @lockfile_dir
74
+ @lockfile_path = File.join(@lockfile_dir, 'lock')
75
+ end
76
+
77
+ # Creates a temporary resource with a random name
78
+ # @return [RefcountedResource]
79
+ def self.make_temp
80
+ name = File.basename(Dir::Tmpname.make_tmpname([resource_class, 'tmp'],
81
+ File.join(resource_dir, resource_class)))
82
+ self.new(name)
83
+ end
84
+
85
+ abstract_method :on_up
86
+ abstract_method :on_down
87
+ abstract_method :on_acquire
88
+ abstract_method :on_release
89
+
90
+ # Executes the given block.
91
+ # @yield calling before on_up once per group of
92
+ # processes using the same resurce, and
93
+ # on_acquire before executing the block.
94
+ # It will execute on_release after executing the
95
+ # block and the last process using the reource
96
+ # will call on_down
97
+ #
98
+ def auto
99
+ begin
100
+ Vmit.logger.debug "Using resource lock #{lockfile_path}"
101
+ File.open(lockfile_path, File::WRONLY | File::CREAT, 0666) do |f|
102
+ begin
103
+ if f.flock File::LOCK_EX | File::LOCK_NB
104
+ # we are the first ones, bring the resource up
105
+ self.on_up
106
+ end
107
+
108
+ if f.flock File::LOCK_SH
109
+ self.on_acquire
110
+ end
111
+
112
+ yield if block_given?
113
+ rescue Exception => e
114
+ Vmit.logger.error e.message
115
+ raise e
116
+ ensure
117
+ if f.flock File::LOCK_EX | File::LOCK_NB
118
+ self.on_down
119
+ end
120
+ on_release
121
+ f.flock File::LOCK_UN
122
+ end
123
+ end
124
+ rescue Exception => e
125
+ Vmit.logger.error e.message
126
+ raise e
127
+ ensure
128
+ File.unlink(lockfile_path)
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ end
data/lib/vmit/utils.rb ADDED
@@ -0,0 +1,103 @@
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 'rubygems'
22
+ require 'open4'
23
+ require 'progressbar'
24
+ require 'digest/sha1'
25
+
26
+ # Taken from Ruby on Rails
27
+ class Hash
28
+ # Returns a hash that represents the difference between two hashes.
29
+ #
30
+ # Examples:
31
+ #
32
+ # {1 => 2}.diff(1 => 2) # => {}
33
+ # {1 => 2}.diff(1 => 3) # => {1 => 2}
34
+ # {}.diff(1 => 2) # => {1 => 2}
35
+ # {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
36
+ def diff(h2)
37
+ dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) })
38
+ end
39
+ end
40
+
41
+ module Vmit
42
+
43
+ module Utils
44
+ # @return [String] random MAC address
45
+ def self.random_mac_address
46
+ ("%02x"%((rand 64).to_i*4|2))+(0..4).inject(""){|s,x|s+":%02x"%(rand 256).to_i}
47
+ end
48
+
49
+ def self.uname(bzimage)
50
+ offset = 0
51
+ File.open(bzimage) do |f|
52
+ f.seek(0x20E)
53
+ offset = f.read(2).unpack('s')[0]
54
+ f.seek(offset + 0x200)
55
+ ver = f.read(128).unpack('Z*')[0]
56
+ return ver
57
+ end
58
+ nil
59
+ end
60
+
61
+ def self.kernel_version(bzimage)
62
+ uname(bzimage).split(' ')[0]
63
+ end
64
+
65
+ def self.sha1_file(filename)
66
+ sha1 = Digest::SHA1.new
67
+ File.open(filename) do |file|
68
+ buffer = ''
69
+ # Read the file 512 bytes at a time
70
+ while not file.eof
71
+ file.read(512, buffer)
72
+ sha1.update(buffer)
73
+ end
74
+ end
75
+ sha1.to_s
76
+ end
77
+
78
+ # @param [String] uri uri to download
79
+ # @param [String] target where to donwload the file (directory)
80
+ def self.download_file(uri, target)
81
+ progress = ProgressBar.new(File.basename(uri.path), 100)
82
+ Net::HTTP.start(uri.host) do |http|
83
+ begin
84
+ file = open(target, 'wb')
85
+ http.request_get(uri.path) do |response|
86
+ dl_size = response.content_length
87
+ already_dl = 0
88
+ response.read_body do |segment|
89
+ already_dl += segment.length
90
+ if(already_dl != 0)
91
+ progress.set((already_dl * 100) / dl_size)
92
+ end
93
+ file.write(segment)
94
+ end
95
+ end
96
+ ensure
97
+ file.close
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+ end