vmit 0.0.3

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