kitchen-qemu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b271e7a403bb173917ba2844c8438c3090fc17fd
4
+ data.tar.gz: 060fadc6ce149e8aa44fcf92f6e33511001d3f1e
5
+ SHA512:
6
+ metadata.gz: 5c211c9f3e8919de20afbea050d1ee7aff057917a5f9ace7f1f8b2eeb0441c88af10a60a3f3996f45b7a51026e3739fad5f0ab5886e46ca26fb85e0ca8b57b89
7
+ data.tar.gz: 85aff6d2b0546d56bafef287b48570fd06445518b2cd7bcc14407383729cc32cc4c5de9d95341e453415168f3e8135e914c5c09436bd264d020e8be455e21c0d
@@ -0,0 +1,250 @@
1
+ # This file is part of kitchen-qemu.
2
+ # Copyright 2016 Emil Renner Berthing <esmil@esmil.dk>
3
+ #
4
+ # kitchen-qemu is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # kitchen-qemu is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with kitchen-qemu. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'open3'
18
+
19
+ require 'kitchen'
20
+ require 'kitchen/driver/qemu_version'
21
+ require 'kitchen/driver/qmpclient'
22
+
23
+ module Kitchen
24
+
25
+ module Driver
26
+
27
+ # QEMU driver for Kitchen.
28
+ #
29
+ # @author Emil Renner Berthing <esmil@esmil.dk>
30
+ class Qemu < Kitchen::Driver::Base
31
+ include ShellOut
32
+
33
+ kitchen_driver_api_version 2
34
+ plugin_version Kitchen::Driver::QEMU_VERSION
35
+
36
+ default_config :arch, 'x86_64'
37
+ default_config :username, 'kitchen'
38
+ default_config :password, 'kitchen'
39
+ default_config :port, 2222
40
+ default_config :memory, '512'
41
+ default_config :nic_model, 'virtio'
42
+
43
+ required_config :image do |attr, value, subject|
44
+ raise UserError, 'Must specify image file' unless value
45
+ end
46
+
47
+ # A lifecycle method that should be invoked when the object is about ready
48
+ # to be used. A reference to an Instance is required as configuration
49
+ # dependant data may be access through an Instance. This also acts as a
50
+ # hook point where the object may wish to perform other last minute
51
+ # checks, validations, or configuration expansions.
52
+ #
53
+ # @param instance [Instance] an associated instance
54
+ # @return [self] itself, for use in chaining
55
+ # @raise [ClientError] if instance parameter is nil
56
+ def finalize_config!(instance)
57
+ super
58
+ if not config[:binary]
59
+ config[:binary] = @@archbinary[config[:arch]] or
60
+ raise UserError, "Unknown architecture '#{config[:arch]}'"
61
+ end
62
+ if not config[:vnc] and not config[:spice]
63
+ config[:spice] = "addr=#{spice_path},unix"
64
+ end
65
+ self
66
+ end
67
+
68
+ # Creates a QEMU instance.
69
+ #
70
+ # @param state [Hash] mutable instance and driver state
71
+ # @raise [ActionFailed] if the action could not be completed
72
+ def create(state)
73
+ monitor = monitor_path
74
+ if File.exist?(monitor)
75
+ begin
76
+ mon = UNIXSocket.new(monitor)
77
+ rescue Errno::ECONNREFUSED
78
+ File.delete(spice_path)
79
+ File.delete(monitor)
80
+ else
81
+ mon.close
82
+ raise ActionFailed, "QEMU instance #{instance.to_str} already running."
83
+ end
84
+ end
85
+
86
+ create_privkey or raise ActionFailed, "Unable to create file '#{privkey_path}'"
87
+
88
+ fqdn = config[:vm_hostname] || instance.name
89
+ hostname = fqdn.match(/^([^.]+)/)[0]
90
+
91
+ state[:hostname] = 'localhost'
92
+ state[:port] = config[:port]
93
+ state[:username] = config[:username]
94
+ state[:password] = config[:password]
95
+
96
+ cmd = [
97
+ config[:binary], '-daemonize',
98
+ '-chardev', "socket,id=monitor,path=#{monitor},server,nowait",
99
+ '-mon', 'chardev=monitor,mode=control,default',
100
+ '-m', config[:memory],
101
+ '-net', "nic,model=#{config[:nic_model]}",
102
+ '-net', "user,net=192.168.1.0/24,hostname=#{hostname},hostfwd=tcp::#{state[:port]}-:22",
103
+ '-device', 'virtio-scsi-pci,id=scsi',
104
+ '-device', 'scsi-hd,drive=root',
105
+ '-drive', "if=none,id=root,readonly,file=#{config[:image]}",
106
+ '-snapshot',
107
+ ]
108
+
109
+ kvm = config[:kvm]
110
+ if kvm.nil? # autodetect
111
+ begin
112
+ kvm = File.stat('/dev/kvm')
113
+ rescue Errno::ENOENT
114
+ kvm = false
115
+ info 'KVM device /dev/kvm doesn\'t exist. Maybe the module is not loaded.'
116
+ else
117
+ kvm = kvm.writable? && kvm.readable?
118
+ info 'KVM device /dev/kvm not read/writeable. Maybe add your user to the kvm group.' unless kvm
119
+ end
120
+ end
121
+ if kvm
122
+ info 'KVM enabled.'
123
+ cmd.push('-enable-kvm', '-cpu', 'host')
124
+ else
125
+ info 'KVM disabled'
126
+ end
127
+
128
+ if config[:spice]
129
+ cmd.push('-vga', 'qxl', '-spice', config[:spice])
130
+ elsif config[:vnc]
131
+ cmd.push('-vnc', config[:vnc])
132
+ end
133
+
134
+ info 'Spawning QEMU..'
135
+ error = nil
136
+ Open3.popen3({ 'QEMU_AUDIO_DRV' => 'none' }, *cmd, :unsetenv_others=>true ) do |_, _, err, thr|
137
+ if not thr.value.success?
138
+ error = err.read.strip
139
+ end
140
+ end
141
+ raise ActionFailed, error if error
142
+
143
+ if hostname == fqdn
144
+ names = fqdn
145
+ else
146
+ names = "#{fqdn} #{hostname}"
147
+ end
148
+
149
+ info 'Waiting for SSH..'
150
+ conn = instance.transport.connection(state)
151
+ conn.wait_until_ready
152
+ conn.execute("sudo sh -c 'echo 127.0.0.1 #{names} >> /etc/hosts' 2>/dev/null")
153
+ conn.execute('install -dm700 "$HOME/.ssh"')
154
+ conn.execute("echo '#{@@pubkey}' > \"$HOME/.ssh/authorized_keys\"")
155
+ conn.close
156
+ state[:ssh_key] = privkey_path
157
+ end
158
+
159
+ # Destroys an instance.
160
+ #
161
+ # @param state [Hash] mutable instance state
162
+ # @raise [ActionFailed] if the action could not be completed
163
+ def destroy(state)
164
+ monitor = monitor_path
165
+ return unless File.exist?(monitor)
166
+
167
+ instance.transport.connection(state).close
168
+
169
+ begin
170
+ mon = QMPClient.new(UNIXSocket.new(monitor), 2)
171
+ info 'Quitting QEMU..'
172
+ mon.execute('quit')
173
+ mon.wait_for_eof(5)
174
+ mon.close
175
+ rescue Errno::ECONNREFUSED
176
+ info 'Connection to monitor refused. Assuming QEMU already quit.'
177
+ rescue QMPClient::Timeout
178
+ mon.close
179
+ raise ActionFailed, "QEMU instance #{instance.to_str} is unresponsive"
180
+ end
181
+
182
+ File.delete(spice_path)
183
+ File.delete(monitor)
184
+ end
185
+
186
+ private
187
+
188
+ @@privkey = %{-----BEGIN RSA PRIVATE KEY-----
189
+ MIIEowIBAAKCAQEAyG6ASL3bWS67rsA5LDvKnfdCBagK61B5LIr+NvdjK3oRKhCq
190
+ qRs7aNSPOqMu2NbKot4/BtD0hWipF7CAsMqK+241coMUwxRTlqvoe/L7xZ24Ktaj
191
+ rm1kk/xNUGP5vWyK8sYfYnDUuLSypRaZ/ZfWuKZgQLDdOw2FWqHFVLoJDsXgsa/y
192
+ i1F3b5l4+F36vN+7pbS9YIMmjdmKng8J/hsTPETZudayEWuAy+mNaz4M7ZtxS/gP
193
+ TIJcxIxC27PLOhA1XsABunaNR8Ety6ghsZX2YEYnnpcyB5Mmqi0VOJs6m5IZn2kf
194
+ 1P3x0KaUQ2Fm9wqAkgYpSccWH4yytVQ8lzlpBwIDAQABAoIBAH3s5w5Msj5C3Un6
195
+ nTEMU82RZlqVbF7RjYANx5ATN6w+IgCSvhZG9Ll1KpPFqI41zNQs295Vc/tJeUtX
196
+ 6lKovk8fu9a5QlcaMzYrxYHydHqBEA9iES5qrlFHp++FEIgRZO8IyPkZOJzfconE
197
+ PHWWayJR7ZFXTXdnlEwP7SHBTCWJ0PIRoNQEkOMX+tsbbS5w5kIpWNUx9YJzLezt
198
+ YSy/9RLLuhioq7ElBM6rgb3jn37W9+yhSsD+9nbQCePS5BoEzrwxZ298pyYpX/2B
199
+ DPKJ5RegClKJqGgBYjcit0n5fyzis01tzPRKGl/VjOFEEcKvoi3TLodORGCQks8q
200
+ 5TVkt+ECgYEA+wRQOq8zcsBq+5+4PjML9KHxtkcug2gfD+5PtY7nvM9ndxmk3DC/
201
+ 87mE30LlXjEHt1zp5XWWsoVadjedZ9b+H5RUS2b3qxgqR6v2GAgGkdz3zf9ZgsO7
202
+ lqHY6pnmKjXGRZ3w5fneVPeI0fQ3l77WpUUOAM9PGVKjpnLKBBKx5DcCgYEAzGkc
203
+ IT8SBPXBd+yYFNNnLjj6jIBnRC/giiRoepO9Ojf2ZGUzFT1sLYG0//gtn/rdru57
204
+ Y0FOwsA6AhYXJa+5oEBdARWdGJIBz9EQbuVidK/wj7wAqvIvOeGp1TnzMxAz1+8v
205
+ CGVw22nxqtCguPzeKEZmzm3kagJuApfxiEp42bECgYEAw8ZHdJ20uKkOR5X4srpJ
206
+ dtDfnlTCGEcbAufRTz9XylDQ13kutXVoIITu9tpL3jzLUd2rpwUhNbcAKPeTUqvB
207
+ o4uievSh8dV1FFUwKOoJhbYbp5SikXRrWD5+2eqSMxWhwCZA/nz1RLuTAH1C5p02
208
+ 98t18ne9r3henrEkkiyqhd0CgYBH2XplxUGUNL34ZVVfnJ9cA/Mth8TElv+aDwoa
209
+ a+vLlvgoednm0VxA8qKohpei8A8T+ges77u7gM3jBdjFCmt5BKasRuidRlUUsyvP
210
+ jxl4Yo9wNmkVrWMkOUn1BRWTEVLnx88EaIOu3CJyJDsaSufbyENCtCXhjVEV4Eqp
211
+ 2WN5QQKBgDHr6hoSHRNWpC6ruuaFF8MGrV8mnvWbCn9KUxQ6zWbm6hQSyEa/3b4d
212
+ kc9GOsm4n+EVnWyhUVjwxEExaCRuFa4aJ/ekLZYtepNnd9Nsoknqd1SVW163QjrY
213
+ tY4IM9IaSC2LuPFVc0Kx6TwObdeQScOokIxL3HfayfLKieTLC+w2
214
+ -----END RSA PRIVATE KEY-----
215
+ }
216
+
217
+ @@pubkey = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDIboBIvdtZLruuwDksO8qd90IFqArrUHksiv4292MrehEqEKqpGzto1I86oy7Y1sqi3j8G0PSFaKkXsICwyor7bjVygxTDFFOWq+h78vvFnbgq1qOubWST/E1QY/m9bIryxh9icNS4tLKlFpn9l9a4pmBAsN07DYVaocVUugkOxeCxr/KLUXdvmXj4Xfq837ultL1ggyaN2YqeDwn+GxM8RNm51rIRa4DL6Y1rPgztm3FL+A9MglzEjELbs8s6EDVewAG6do1HwS3LqCGxlfZgRieelzIHkyaqLRU4mzqbkhmfaR/U/fHQppRDYWb3CoCSBilJxxYfjLK1VDyXOWkH kitchen-qemu'
218
+
219
+ @@archbinary = {
220
+ 'i386' => 'qemu-system-i386',
221
+ 'amd64' => 'qemu-system-x86_64',
222
+ 'x86' => 'qemu-system-i386',
223
+ 'x86_64' => 'qemu-system-x86_64',
224
+ '32bit' => 'qemu-system-i386',
225
+ '64bit' => 'qemu-system-x86_64',
226
+ }
227
+
228
+ def privkey_path
229
+ File.join(config[:kitchen_root], '.kitchen', 'kitchen-qemu.key')
230
+ end
231
+
232
+ def monitor_path
233
+ File.join(config[:kitchen_root], '.kitchen', "#{instance.name}.mon")
234
+ end
235
+
236
+ def spice_path
237
+ File.join(config[:kitchen_root], '.kitchen', "#{instance.name}.spice")
238
+ end
239
+
240
+ def create_privkey
241
+ path = privkey_path
242
+ return true if File.file?(path)
243
+ File.open(path, File::CREAT|File::TRUNC|File::RDWR, 0600) { |f| f.write(@@privkey) }
244
+ end
245
+
246
+ end
247
+ end
248
+ end
249
+
250
+ # vim: set ts=2 sw=2 et:
@@ -0,0 +1,22 @@
1
+ # This file is part of kitchen-qemu.
2
+ # Copyright 2016 Emil Renner Berthing <esmil@esmil.dk>
3
+ #
4
+ # kitchen-qemu is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # kitchen-qemu is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with kitchen-qemu. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module Kitchen
18
+ module Driver
19
+ # Version string for the QEMU Kitchen driver
20
+ QEMU_VERSION = '0.1.0'
21
+ end
22
+ end
@@ -0,0 +1,98 @@
1
+ # This file is part of kitchen-qemu.
2
+ # Copyright 2016 Emil Renner Berthing <esmil@esmil.dk>
3
+ #
4
+ # kitchen-qemu is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # kitchen-qemu is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with kitchen-qemu. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'json'
18
+ require 'socket'
19
+
20
+ module Kitchen
21
+ module Driver
22
+ class QMPClient
23
+ class Timeout < Exception
24
+ end
25
+
26
+ def initialize(io, timeout = 1)
27
+ @io = io
28
+ @ioa = [ io ]
29
+ @timeout = timeout
30
+ @buf = []
31
+ readnext(timeout) or raise Timeout
32
+ execute('qmp_capabilities') or raise Timeout
33
+ self
34
+ end
35
+
36
+ def execute(cmd, timeout = @timeout)
37
+ send( 'execute' => cmd )
38
+ loop do
39
+ ret = readnext(timeout) or raise Timeout
40
+ if ret['return']
41
+ return ret['return']
42
+ end
43
+ end
44
+ end
45
+
46
+ def wait_for_eof(timeout = @timeout)
47
+ while IO.select(@ioa, nil, nil, timeout)
48
+ begin
49
+ @io.read_nonblock(4096)
50
+ rescue EOFError
51
+ return
52
+ rescue Errno::ECONNRESET
53
+ return
54
+ rescue IO::WaitReadable
55
+ # do notheng
56
+ end
57
+ end
58
+ raise Timeout
59
+ end
60
+
61
+ def close
62
+ @io.close
63
+ end
64
+
65
+ private
66
+
67
+ def send(obj)
68
+ @io.write("#{obj.to_json}\r\n")
69
+ end
70
+
71
+ def readnext(timeout)
72
+ loop do
73
+ if not @buf.empty? and @buf.last.match("\n")
74
+ s = @buf.pop.split("\n", 2)
75
+ @buf.push(s[0])
76
+ obj = JSON.parse(@buf.join(''))
77
+ @buf.clear
78
+ @buf.push(s[1]) unless s[1].empty?
79
+ return obj
80
+ end
81
+
82
+ loop do
83
+ return nil unless IO.select(@ioa, nil, nil, timeout)
84
+ begin
85
+ @buf.push(@io.read_nonblock(4096))
86
+ rescue IO::WaitReadable
87
+ # do notheng
88
+ else
89
+ break
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ # vim: set ts=2 sw=2 et:
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-qemu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Emil Renner Berthing
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-kitchen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ description: Kitchen::Driver::Qemu - A QEMU Driver for Test Kitchen.
28
+ email:
29
+ - esmil@esmil.dk
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/kitchen/driver/qemu.rb
35
+ - lib/kitchen/driver/qemu_version.rb
36
+ - lib/kitchen/driver/qmpclient.rb
37
+ homepage: https://github.com/esmil/kitchen-qemu/
38
+ licenses:
39
+ - GPL-3.0+
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 2.5.1
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Kitchen::Driver::Qemu - A QEMU Driver for Test Kitchen.
61
+ test_files: []