kitchen-qemu 0.1.0

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,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: []