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.
@@ -1,299 +0,0 @@
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
-
27
- require 'vmit/utils'
28
-
29
- module Vmit
30
-
31
- class VirtualMachine
32
-
33
- attr_accessor :work_dir
34
-
35
- VM_DEFAULTS = {
36
- :memory => '1G',
37
- }
38
- SWITCH = 'br0'
39
-
40
- # Accessor to current options
41
- def [](key)
42
- @opts[key]
43
- end
44
-
45
- def config_file
46
- File.join(work_dir, 'config.yml')
47
- end
48
-
49
- def initialize(work_dir)
50
- @pidfile = PidFile.new(:piddir => work_dir, :pidfile => "vmit.pid")
51
- @work_dir = work_dir
52
-
53
- @opts = {}
54
- @opts.merge!(VM_DEFAULTS)
55
-
56
- if File.exist?(config_file)
57
- @opts.merge!(YAML::load(File.open(config_file)))
58
- end
59
-
60
- # By default the following keys are useful to be
61
- # generated if they don't exist and then use the
62
- # same in the future UNLESS they are
63
- # overriden with vmit run
64
- if not @opts.has_key?(:mac_address)
65
- @opts[:mac_address] = Vmit::Utils.random_mac_address
66
- end
67
-
68
- if not @opts.has_key?(:uuid)
69
- @opts[:uuid] = File.read("/proc/sys/kernel/random/uuid").strip
70
- end
71
-
72
- @network = if @opts.has_key?(:network)
73
- Network.create(@opts[:network])
74
- else
75
- Vmit.logger.info 'No network selected. Using default.'
76
- Network.default
77
- end
78
- Vmit.logger.info "Network: #{@network}"
79
- end
80
-
81
- # @return [Array,<String>] sorted list of snapshots
82
- def disk_images
83
- Dir.glob(File.join(work_dir, '*.qcow2')).sort do |a,b|
84
- File.ctime(a) <=> File.ctime(b)
85
- end
86
- end
87
-
88
- # Takes a disk snapshot
89
- def disk_snapshot!
90
- disk_image_shift!
91
- end
92
-
93
- def disk_image_init!(opts={})
94
- disk_image_shift!(opts)
95
- end
96
-
97
- DISK_INIT_DEFAULTS = {:disk_size => '10G'}
98
-
99
- # Shifts an image, adding a new one using the
100
- # previous newest one as backing file
101
- #
102
- # @param [Hash] opts options for the disk shift
103
- # @option opts [String] :disk_size Disk size. Only used for image creation
104
- def disk_image_shift!(opts={})
105
- runtime_opts = DISK_INIT_DEFAULTS.merge(opts)
106
-
107
- file_name = File.join(work_dir, "sda-#{Time.now.to_i}.qcow2")
108
- images = disk_images
109
-
110
- file_name = 'base.qcow2' if images.size == 0
111
-
112
- args = ['/usr/bin/qemu-img', 'create',
113
- '-f', "qcow2"]
114
-
115
- if not images.empty?
116
- args << '-b'
117
- args << images.last
118
- end
119
- args << file_name
120
- if images.empty?
121
- args << runtime_opts[:disk_size]
122
- end
123
-
124
- Vmit.logger.info "Shifted image. Current is '#{file_name}'."
125
- Cheetah.run(*args)
126
- end
127
-
128
- # Rolls back to the previous snapshot
129
- def disk_rollback!
130
- images = disk_images
131
-
132
- return if images.empty?
133
-
134
- if images.size == 1
135
- Vmit.logger.fatal "Only the base snapshot left!"
136
- return
137
- end
138
- Vmit.logger.info "Removing #{images.last}"
139
- FileUtils.rm(images.last)
140
- end
141
-
142
- # @returns [String] The latest COW snapshot
143
- def current_image
144
- curr = disk_images.last
145
- raise "No hard disk image available" if curr.nil?
146
- curr
147
- end
148
-
149
- def options
150
- @opts
151
- end
152
-
153
- # @return [Hash] Config of the virtual machine
154
- # This is all options plus the defaults
155
- def config
156
- VM_DEFAULTS.merge(@opts)
157
- end
158
-
159
- # @return [Hash] config that differs from default
160
- # and therefore relevant to be persisted in config.yml
161
- def relevant_config
162
- config.diff(VM_DEFAULTS)
163
- end
164
-
165
- # Saves the configuration in config.yml
166
- def save_config!
167
- if not relevant_config.empty?
168
- Vmit.logger.info "Writing config.yml..."
169
- File.open(config_file, 'w') do |f|
170
- f.write(relevant_config.to_yaml)
171
- end
172
- end
173
- end
174
-
175
- def to_s
176
- config.to_s
177
- end
178
-
179
- BINDIR = File.join(File.dirname(__FILE__), '../../bin')
180
-
181
- # Starts the virtual machine
182
- #
183
- # @param [Hash] runtime_opts Runtime options
184
- # @option runtime_opts [String] :cdrom CDROM image
185
- # @option runtime_opts [String] :kernel Kernel image
186
- # @option runtime_opts [String] :initrd initrd image
187
- # @option runtime_opts [String] :append Kernel command line
188
- # @option runtime_opts [String] :floppy Floppy (image or directory)
189
- def run(runtime_opts)
190
- Vmit.logger.info "Starting VM..."
191
- # Don't overwrite @opts so that
192
- # run can be called various times
193
- opts = {}
194
- opts.merge!(@opts)
195
- opts.merge!(runtime_opts)
196
-
197
- config.each do |k,v|
198
- Vmit.logger.info " => #{k} : #{v}"
199
- end
200
-
201
- begin
202
- # HACK, will be replaced by a better config system
203
- use_virtio = ! File.exist?(File.join(work_dir, '.disable-virtio'))
204
-
205
- ifup = File.expand_path(File.join(BINDIR, 'vmit-ifup'))
206
- ifdown = File.expand_path(File.join(BINDIR, 'vmit-ifdown'))
207
-
208
- args = ['/usr/bin/qemu-kvm', '-boot', 'c',
209
- '-m', "#{opts[:memory]}",
210
- '-pidfile', File.join(work_dir, 'qemu.pid')]
211
-
212
- if use_virtio
213
- args << '-drive'
214
- args << "file=#{current_image},if=virtio"
215
-
216
- args << '-netdev'
217
- args << "type=tap,script=#{ifup},downscript=#{ifdown},id=vnet0"
218
- args << '-device'
219
- args << "virtio-net-pci,netdev=vnet0,mac=#{opts[:mac_address]}"
220
- else
221
- args << '-drive'
222
- args << "file=#{current_image}"
223
-
224
- args << '-net'
225
- args << "nic,macaddr=#{opts[:mac_address]}"
226
- args << '-net'
227
- args << "tap,script=#{ifup},downscript=#{ifdown}"
228
- end
229
-
230
- # advanced options, mostly to be used by plugins
231
- [:cdrom, :kernel, :initrd, :append].each do |key|
232
- if opts.has_key?(key)
233
- args << "-#{key}"
234
- args << case opts[key]
235
- # append is multple
236
- when Array then opts[key].join(' ')
237
- else opts[key]
238
- end
239
- end
240
- end
241
-
242
- if opts.has_key?(:floppy)
243
- if File.directory?(opts[:floppy])
244
- args << '-fda'
245
- args << "fat:floppy:#{opts[:floppy]}"
246
- else
247
- Vmit.logger.warn "#{opts[:floppy]} : only directories supported"
248
- end
249
- end
250
-
251
- # options that translate to
252
- # -no-something if :something => false
253
- [:reboot].each do |key|
254
- if opts.has_key?(key)
255
- # default is true
256
- args << "-no-#{key}" if not opts[key]
257
- end
258
- end
259
-
260
- unless ENV['DISABLE_UUID']
261
- args << '-uuid'
262
- args << "#{opts[:uuid]}"
263
- end
264
-
265
- DRb.start_service nil, self
266
- ENV['VMIT_SERVER'] = DRb.uri
267
-
268
- ENV['VMIT_SWITCH'] = SWITCH
269
- Vmit.logger.debug "Vmit server listening at #{DRb.uri}"
270
-
271
- @network.auto do
272
- begin
273
- Cheetah.run(*args)
274
- ensure
275
- FileUtils.rm_f File.join(work_dir, 'qemu.pid')
276
- end
277
- end
278
- rescue PidFile::DuplicateProcessError => e
279
- Vmit.logger.fatal "VM in '#{work_dir}'' is already running (#{e})"
280
- raise
281
- end
282
- end
283
-
284
- # Called by vmit-ifup
285
- def ifup(device)
286
- Vmit.logger.info " Bringing interface #{device} up"
287
- Cheetah.run '/sbin/ifconfig', device, '0.0.0.0', 'up'
288
- @network.connect_interface(device)
289
- end
290
-
291
- # Called by vmit-ifdown
292
- def ifdown(device)
293
- Vmit.logger.info " Bringing down interface #{device}"
294
- Cheetah.run '/sbin/ifconfig', device, '0.0.0.0', 'down'
295
- @network.disconnect_interface(device)
296
- end
297
- end
298
-
299
- end