summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrick Vacek <patrickvacek@gmail.com>2019-01-25 14:21:26 +0100
committerPatrick Vacek <patrickvacek@gmail.com>2019-01-29 16:39:33 +0100
commita20b2fba46607121031d400400c16d0f033b8a94 (patch)
treedc8bc4b62889c3d184948c611ed72681a848fd7d
parent809f438a498ba5cef59133541629dfff73346ad1 (diff)
downloadmeta-updater-a20b2fba46607121031d400400c16d0f033b8a94.tar.gz
First attempt at backporting oe-selftest to morty.
Still failing: updater.SecondaryTests updater.SotaToolsTests updater.AktualizrToolsTests updater.HsmTests updater.ImplProvTests Mostly seems related to running host-side tools. Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
l---------lib/oeqa/selftest/qemucommand.py1
-rw-r--r--lib/oeqa/selftest/updater.py732
2 files changed, 733 insertions, 0 deletions
diff --git a/lib/oeqa/selftest/qemucommand.py b/lib/oeqa/selftest/qemucommand.py
new file mode 120000
index 0000000..bc06dde
--- /dev/null
+++ b/lib/oeqa/selftest/qemucommand.py
@@ -0,0 +1 @@
../../../scripts/qemucommand.py \ No newline at end of file
diff --git a/lib/oeqa/selftest/updater.py b/lib/oeqa/selftest/updater.py
new file mode 100644
index 0000000..d189323
--- /dev/null
+++ b/lib/oeqa/selftest/updater.py
@@ -0,0 +1,732 @@
1# pylint: disable=C0111,C0325
2import os
3import logging
4import re
5import subprocess
6import unittest
7from time import sleep
8
9from oeqa.selftest.base import oeSelfTest
10from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
11from oeqa.selftest.qemucommand import QemuCommand
12
13
14class SotaToolsTests(oeSelfTest):
15
16 @classmethod
17 def setUpClass(cls):
18 super(SotaToolsTests, cls).setUpClass()
19 logger = logging.getLogger("selftest")
20 logger.info('Running bitbake to build aktualizr-native tools')
21 bitbake('aktualizr-native')
22
23 def test_push_help(self):
24 akt_native_run(self, 'garage-push --help')
25
26 def test_deploy_help(self):
27 akt_native_run(self, 'garage-deploy --help')
28
29 def test_garagesign_help(self):
30 akt_native_run(self, 'garage-sign --help')
31
32
33class GeneralTests(oeSelfTest):
34
35 def test_feature_sota(self):
36 result = get_bb_var('DISTRO_FEATURES').find('sota')
37 self.assertNotEqual(result, -1, 'Feature "sota" not set at DISTRO_FEATURES')
38
39 def test_feature_systemd(self):
40 result = get_bb_var('DISTRO_FEATURES').find('systemd')
41 self.assertNotEqual(result, -1, 'Feature "systemd" not set at DISTRO_FEATURES')
42
43 def test_credentials(self):
44 logger = logging.getLogger("selftest")
45 logger.info('Running bitbake to build core-image-minimal')
46 self.append_config('SOTA_CLIENT_PROV = "aktualizr-auto-prov"')
47 bitbake('core-image-minimal')
48 credentials = get_bb_var('SOTA_PACKED_CREDENTIALS')
49 # skip the test if the variable SOTA_PACKED_CREDENTIALS is not set
50 if credentials is None:
51 raise unittest.SkipTest("Variable 'SOTA_PACKED_CREDENTIALS' not set.")
52 # Check if the file exists
53 self.assertTrue(os.path.isfile(credentials), "File %s does not exist" % credentials)
54 deploydir = get_bb_var('DEPLOY_DIR_IMAGE')
55 imagename = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal')
56 # Check if the credentials are included in the output image
57 result = runCmd('tar -jtvf %s/%s.tar.bz2 | grep sota_provisioning_credentials.zip' %
58 (deploydir, imagename), ignore_status=True)
59 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
60
61 def test_java(self):
62 result = runCmd('which java', ignore_status=True)
63 self.assertEqual(result.status, 0,
64 "Java not found. Do you have a JDK installed on your host machine?")
65
66 def test_add_package(self):
67 deploydir = get_bb_var('DEPLOY_DIR_IMAGE')
68 imagename = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal')
69 image_path = deploydir + '/' + imagename + '.otaimg'
70 logger = logging.getLogger("selftest")
71
72 logger.info('Running bitbake with man in the image package list')
73 self.append_config('IMAGE_INSTALL_append = " man "')
74 bitbake('-c cleanall man')
75 bitbake('core-image-minimal')
76 result = runCmd('oe-pkgdata-util find-path /usr/bin/man')
77 self.assertEqual(result.output, 'man: /usr/bin/man')
78 path1 = os.path.realpath(image_path)
79 size1 = os.path.getsize(path1)
80 logger.info('First image %s has size %i' % (path1, size1))
81
82 logger.info('Running bitbake without man in the image package list')
83 self.append_config('IMAGE_INSTALL_remove = " man "')
84 bitbake('-c cleanall man')
85 bitbake('core-image-minimal')
86 result = runCmd('oe-pkgdata-util find-path /usr/bin/man', ignore_status=True)
87 self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output)
88 self.assertEqual(result.output, 'ERROR: Unable to find any package producing path /usr/bin/man')
89 path2 = os.path.realpath(image_path)
90 size2 = os.path.getsize(path2)
91 logger.info('Second image %s has size %i', path2, size2)
92 self.assertNotEqual(path1, path2, "Image paths are identical; image was not rebuilt.")
93 self.assertNotEqual(size1, size2, "Image sizes are identical; image was not rebuilt.")
94
95
96class AktualizrToolsTests(oeSelfTest):
97
98 @classmethod
99 def setUpClass(cls):
100 super(AktualizrToolsTests, cls).setUpClass()
101 logger = logging.getLogger("selftest")
102 logger.info('Running bitbake to build aktualizr-native tools')
103 bitbake('aktualizr-native')
104
105 def test_cert_provider_help(self):
106 akt_native_run(self, 'aktualizr-cert-provider --help')
107
108 def test_cert_provider_local_output(self):
109 logger = logging.getLogger("selftest")
110 logger.info('Running bitbake to build aktualizr-ca-implicit-prov')
111 bitbake('aktualizr-ca-implicit-prov')
112 bb_vars = get_bb_vars(['SOTA_PACKED_CREDENTIALS', 'T'], 'aktualizr-native')
113 creds = bb_vars['SOTA_PACKED_CREDENTIALS']
114 temp_dir = bb_vars['T']
115 bb_vars_prov = get_bb_vars(['STAGING_DIR_HOST', 'libdir'], 'aktualizr-ca-implicit-prov')
116 config = bb_vars_prov['STAGING_DIR_HOST'] + bb_vars_prov['libdir'] + '/sota/sota_implicit_prov_ca.toml'
117
118 akt_native_run(self, 'aktualizr-cert-provider -c {creds} -r -l {temp} -g {config}'
119 .format(creds=creds, temp=temp_dir, config=config))
120
121 # Might be nice if these names weren't hardcoded.
122 cert_path = temp_dir + '/var/sota/import/client.pem'
123 self.assertTrue(os.path.isfile(cert_path), "Client certificate not found at %s." % cert_path)
124 self.assertTrue(os.path.getsize(cert_path) > 0, "Client certificate at %s is empty." % cert_path)
125 pkey_path = temp_dir + '/var/sota/import/pkey.pem'
126 self.assertTrue(os.path.isfile(pkey_path), "Private key not found at %s." % pkey_path)
127 self.assertTrue(os.path.getsize(pkey_path) > 0, "Private key at %s is empty." % pkey_path)
128 ca_path = temp_dir + '/var/sota/import/root.crt'
129 self.assertTrue(os.path.isfile(ca_path), "Client certificate not found at %s." % ca_path)
130 self.assertTrue(os.path.getsize(ca_path) > 0, "Client certificate at %s is empty." % ca_path)
131
132
133class AutoProvTests(oeSelfTest):
134
135 def setUpLocal(self):
136 layer = "meta-updater-qemux86-64"
137 result = runCmd('bitbake-layers show-layers')
138 if re.search(layer, result.output) is None:
139 # Assume the directory layout for finding other layers. We could also
140 # make assumptions by using 'show-layers', but either way, if the
141 # layers we need aren't where we expect them, we are out of luck.
142 path = os.path.abspath(os.path.dirname(__file__))
143 metadir = path + "/../../../../"
144 self.meta_qemu = metadir + layer
145 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
146 else:
147 self.meta_qemu = None
148 self.append_config('MACHINE = "qemux86-64"')
149 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
150 self.qemu, self.s = qemu_launch(machine='qemux86-64')
151
152 def tearDownLocal(self):
153 qemu_terminate(self.s)
154 if self.meta_qemu:
155 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
156
157 def qemu_command(self, command):
158 return qemu_send_command(self.qemu.ssh_port, command)
159
160 def test_provisioning(self):
161 print('Checking machine name (hostname) of device:')
162 stdout, stderr, retcode = self.qemu_command('hostname')
163 self.assertEqual(retcode, 0, "Unable to check hostname. " +
164 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
165 machine = get_bb_var('MACHINE', 'core-image-minimal')
166 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
167 # Strip off line ending.
168 value = stdout.decode()[:-1]
169 self.assertEqual(value, machine,
170 'MACHINE does not match hostname: ' + machine + ', ' + value)
171 print(value)
172 print('Checking output of aktualizr-info:')
173 ran_ok = False
174 for delay in [1, 2, 5, 10, 15]:
175 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
176 if retcode == 0 and stderr == b'':
177 ran_ok = True
178 break
179 sleep(delay)
180 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
181
182 verifyProvisioned(self, machine)
183
184
185class ManualControlTests(oeSelfTest):
186
187 def setUpLocal(self):
188 layer = "meta-updater-qemux86-64"
189 result = runCmd('bitbake-layers show-layers')
190 if re.search(layer, result.output) is None:
191 # Assume the directory layout for finding other layers. We could also
192 # make assumptions by using 'show-layers', but either way, if the
193 # layers we need aren't where we expect them, we are out of like.
194 path = os.path.abspath(os.path.dirname(__file__))
195 metadir = path + "/../../../../"
196 self.meta_qemu = metadir + layer
197 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
198 else:
199 self.meta_qemu = None
200 self.append_config('MACHINE = "qemux86-64"')
201 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
202 self.append_config('SYSTEMD_AUTO_ENABLE_aktualizr = "disable"')
203 self.qemu, self.s = qemu_launch(machine='qemux86-64')
204
205 def tearDownLocal(self):
206 qemu_terminate(self.s)
207 if self.meta_qemu:
208 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
209
210 def qemu_command(self, command):
211 return qemu_send_command(self.qemu.ssh_port, command)
212
213 def test_manual_running_mode_once(self):
214 """
215 Disable the systemd service then run aktualizr manually
216 """
217 sleep(20)
218 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
219 self.assertIn(b'Can\'t open database', stdout,
220 'Aktualizr should not have run yet' + stderr.decode() + stdout.decode())
221
222 stdout, stderr, retcode = self.qemu_command('aktualizr --running-mode=once')
223
224 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
225 self.assertIn(b'Fetched metadata: yes', stdout,
226 'Aktualizr should have run' + stderr.decode() + stdout.decode())
227
228
229class RpiTests(oeSelfTest):
230
231 def setUpLocal(self):
232 # Add layers before changing the machine type, otherwise the sanity
233 # checker complains loudly.
234 layer_python = "meta-openembedded/meta-python"
235 layer_rpi = "meta-raspberrypi"
236 layer_upd_rpi = "meta-updater-raspberrypi"
237 result = runCmd('bitbake-layers show-layers')
238 # Assume the directory layout for finding other layers. We could also
239 # make assumptions by using 'show-layers', but either way, if the
240 # layers we need aren't where we expect them, we are out of luck.
241 path = os.path.abspath(os.path.dirname(__file__))
242 metadir = path + "/../../../../"
243 if re.search(layer_python, result.output) is None:
244 self.meta_python = metadir + layer_python
245 runCmd('bitbake-layers add-layer "%s"' % self.meta_python)
246 else:
247 self.meta_python = None
248 if re.search(layer_rpi, result.output) is None:
249 self.meta_rpi = metadir + layer_rpi
250 runCmd('bitbake-layers add-layer "%s"' % self.meta_rpi)
251 else:
252 self.meta_rpi = None
253 if re.search(layer_upd_rpi, result.output) is None:
254 self.meta_upd_rpi = metadir + layer_upd_rpi
255 runCmd('bitbake-layers add-layer "%s"' % self.meta_upd_rpi)
256 else:
257 self.meta_upd_rpi = None
258
259 # This is trickier that I would've thought. The fundamental problem is
260 # that the qemu layer changes the u-boot file extension to .rom, but
261 # raspberrypi still expects .bin. To prevent this, the qemu layer must
262 # be temporarily removed if it is present. It has to be removed by name
263 # without the complete path, but to add it back when we are done, we
264 # need the full path.
265 p = re.compile(r'meta-updater-qemux86-64\s*(\S*meta-updater-qemux86-64)\s')
266 m = p.search(result.output)
267 if m and m.lastindex > 0:
268 self.meta_qemu = m.group(1)
269 runCmd('bitbake-layers remove-layer meta-updater-qemux86-64')
270 else:
271 self.meta_qemu = None
272
273 self.append_config('MACHINE = "raspberrypi3"')
274 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
275
276 def tearDownLocal(self):
277 if self.meta_qemu:
278 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu, ignore_status=True)
279 if self.meta_upd_rpi:
280 runCmd('bitbake-layers remove-layer "%s"' % self.meta_upd_rpi, ignore_status=True)
281 if self.meta_rpi:
282 runCmd('bitbake-layers remove-layer "%s"' % self.meta_rpi, ignore_status=True)
283 if self.meta_python:
284 runCmd('bitbake-layers remove-layer "%s"' % self.meta_python, ignore_status=True)
285
286 def test_rpi(self):
287 logger = logging.getLogger("selftest")
288 logger.info('Running bitbake to build rpi-basic-image')
289 self.append_config('SOTA_CLIENT_PROV = "aktualizr-auto-prov"')
290 bitbake('rpi-basic-image')
291 credentials = get_bb_var('SOTA_PACKED_CREDENTIALS')
292 # Skip the test if the variable SOTA_PACKED_CREDENTIALS is not set.
293 if credentials is None:
294 raise unittest.SkipTest("Variable 'SOTA_PACKED_CREDENTIALS' not set.")
295 # Check if the file exists.
296 self.assertTrue(os.path.isfile(credentials), "File %s does not exist" % credentials)
297 deploydir = get_bb_var('DEPLOY_DIR_IMAGE')
298 imagename = get_bb_var('IMAGE_LINK_NAME', 'rpi-basic-image')
299 # Check if the credentials are included in the output image.
300 result = runCmd('tar -jtvf %s/%s.tar.bz2 | grep sota_provisioning_credentials.zip' %
301 (deploydir, imagename), ignore_status=True)
302 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
303
304
305class GrubTests(oeSelfTest):
306
307 def setUpLocal(self):
308 layer_intel = "meta-intel"
309 layer_minnow = "meta-updater-minnowboard"
310 result = runCmd('bitbake-layers show-layers')
311 # Assume the directory layout for finding other layers. We could also
312 # make assumptions by using 'show-layers', but either way, if the
313 # layers we need aren't where we expect them, we are out of luck.
314 path = os.path.abspath(os.path.dirname(__file__))
315 metadir = path + "/../../../../"
316 if re.search(layer_intel, result.output) is None:
317 self.meta_intel = metadir + layer_intel
318 runCmd('bitbake-layers add-layer "%s"' % self.meta_intel)
319 else:
320 self.meta_intel = None
321 if re.search(layer_minnow, result.output) is None:
322 self.meta_minnow = metadir + layer_minnow
323 runCmd('bitbake-layers add-layer "%s"' % self.meta_minnow)
324 else:
325 self.meta_minnow = None
326 self.append_config('MACHINE = "intel-corei7-64"')
327 self.append_config('OSTREE_BOOTLOADER = "grub"')
328 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
329 self.qemu, self.s = qemu_launch(efi=True, machine='intel-corei7-64')
330
331 def tearDownLocal(self):
332 qemu_terminate(self.s)
333 if self.meta_intel:
334 runCmd('bitbake-layers remove-layer "%s"' % self.meta_intel, ignore_status=True)
335 if self.meta_minnow:
336 runCmd('bitbake-layers remove-layer "%s"' % self.meta_minnow, ignore_status=True)
337
338 def qemu_command(self, command):
339 return qemu_send_command(self.qemu.ssh_port, command)
340
341 def test_grub(self):
342 print('Checking machine name (hostname) of device:')
343 stdout, stderr, retcode = self.qemu_command('hostname')
344 self.assertEqual(retcode, 0, "Unable to check hostname. " +
345 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
346 machine = get_bb_var('MACHINE', 'core-image-minimal')
347 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
348 # Strip off line ending.
349 value = stdout.decode()[:-1]
350 self.assertEqual(value, machine,
351 'MACHINE does not match hostname: ' + machine + ', ' + value +
352 '\nIs TianoCore ovmf installed on your host machine?')
353 print(value)
354 print('Checking output of aktualizr-info:')
355 ran_ok = False
356 for delay in [1, 2, 5, 10, 15]:
357 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
358 if retcode == 0 and stderr == b'':
359 ran_ok = True
360 break
361 sleep(delay)
362 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
363
364 verifyProvisioned(self, machine)
365
366
367class ImplProvTests(oeSelfTest):
368
369 def setUpLocal(self):
370 layer = "meta-updater-qemux86-64"
371 result = runCmd('bitbake-layers show-layers')
372 if re.search(layer, result.output) is None:
373 # Assume the directory layout for finding other layers. We could also
374 # make assumptions by using 'show-layers', but either way, if the
375 # layers we need aren't where we expect them, we are out of luck.
376 path = os.path.abspath(os.path.dirname(__file__))
377 metadir = path + "/../../../../"
378 self.meta_qemu = metadir + layer
379 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
380 else:
381 self.meta_qemu = None
382 self.append_config('MACHINE = "qemux86-64"')
383 self.append_config('SOTA_CLIENT_PROV = " aktualizr-ca-implicit-prov "')
384 self.append_config('SOTA_DEPLOY_CREDENTIALS = "0"')
385 runCmd('bitbake -c cleanall aktualizr aktualizr-ca-implicit-prov')
386 self.qemu, self.s = qemu_launch(machine='qemux86-64')
387
388 def tearDownLocal(self):
389 qemu_terminate(self.s)
390 if self.meta_qemu:
391 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
392
393 def qemu_command(self, command):
394 return qemu_send_command(self.qemu.ssh_port, command)
395
396 def test_provisioning(self):
397 print('Checking machine name (hostname) of device:')
398 stdout, stderr, retcode = self.qemu_command('hostname')
399 self.assertEqual(retcode, 0, "Unable to check hostname. " +
400 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
401 machine = get_bb_var('MACHINE', 'core-image-minimal')
402 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
403 # Strip off line ending.
404 value = stdout.decode()[:-1]
405 self.assertEqual(value, machine,
406 'MACHINE does not match hostname: ' + machine + ', ' + value)
407 print(value)
408 print('Checking output of aktualizr-info:')
409 ran_ok = False
410 for delay in [1, 2, 5, 10, 15]:
411 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
412 if retcode == 0 and stderr == b'':
413 ran_ok = True
414 break
415 sleep(delay)
416 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
417 # Verify that device has NOT yet provisioned.
418 self.assertIn(b'Couldn\'t load device ID', stdout,
419 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
420 self.assertIn(b'Couldn\'t load ECU serials', stdout,
421 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
422 self.assertIn(b'Provisioned on server: no', stdout,
423 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
424 self.assertIn(b'Fetched metadata: no', stdout,
425 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
426
427 # Run cert_provider.
428 bb_vars = get_bb_vars(['SOTA_PACKED_CREDENTIALS'], 'aktualizr-native')
429 creds = bb_vars['SOTA_PACKED_CREDENTIALS']
430 bb_vars_prov = get_bb_vars(['STAGING_DIR_HOST', 'libdir'], 'aktualizr-ca-implicit-prov')
431 config = bb_vars_prov['STAGING_DIR_HOST'] + bb_vars_prov['libdir'] + '/sota/sota_implicit_prov_ca.toml'
432
433 print('Provisining at root@localhost:%d' % self.qemu.ssh_port)
434 akt_native_run(self, 'aktualizr-cert-provider -c {creds} -t root@localhost -p {port} -s -u -r -g {config}'
435 .format(creds=creds, port=self.qemu.ssh_port, config=config))
436
437 verifyProvisioned(self, machine)
438
439
440class HsmTests(oeSelfTest):
441
442 def setUpLocal(self):
443 layer = "meta-updater-qemux86-64"
444 result = runCmd('bitbake-layers show-layers')
445 if re.search(layer, result.output) is None:
446 # Assume the directory layout for finding other layers. We could also
447 # make assumptions by using 'show-layers', but either way, if the
448 # layers we need aren't where we expect them, we are out of luck.
449 path = os.path.abspath(os.path.dirname(__file__))
450 metadir = path + "/../../../../"
451 self.meta_qemu = metadir + layer
452 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
453 else:
454 self.meta_qemu = None
455 self.append_config('MACHINE = "qemux86-64"')
456 self.append_config('SOTA_CLIENT_PROV = "aktualizr-hsm-prov"')
457 self.append_config('SOTA_DEPLOY_CREDENTIALS = "0"')
458 self.append_config('SOTA_CLIENT_FEATURES = "hsm"')
459 self.append_config('IMAGE_INSTALL_append = " softhsm-testtoken"')
460 runCmd('bitbake -c cleanall aktualizr aktualizr-hsm-prov')
461 self.qemu, self.s = qemu_launch(machine='qemux86-64')
462
463 def tearDownLocal(self):
464 qemu_terminate(self.s)
465 if self.meta_qemu:
466 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
467
468 def qemu_command(self, command):
469 return qemu_send_command(self.qemu.ssh_port, command)
470
471 def test_provisioning(self):
472 print('Checking machine name (hostname) of device:')
473 stdout, stderr, retcode = self.qemu_command('hostname')
474 self.assertEqual(retcode, 0, "Unable to check hostname. " +
475 "Is an ssh daemon (such as dropbear or openssh) installed on the device?")
476 machine = get_bb_var('MACHINE', 'core-image-minimal')
477 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
478 # Strip off line ending.
479 value = stdout.decode()[:-1]
480 self.assertEqual(value, machine,
481 'MACHINE does not match hostname: ' + machine + ', ' + value)
482 print(value)
483 print('Checking output of aktualizr-info:')
484 ran_ok = False
485 for delay in [1, 2, 5, 10, 15]:
486 stdout, stderr, retcode = self.qemu_command('aktualizr-info')
487 if retcode == 0 and stderr == b'':
488 ran_ok = True
489 break
490 sleep(delay)
491 self.assertTrue(ran_ok, 'aktualizr-info failed: ' + stderr.decode() + stdout.decode())
492 # Verify that device has NOT yet provisioned.
493 self.assertIn(b'Couldn\'t load device ID', stdout,
494 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
495 self.assertIn(b'Couldn\'t load ECU serials', stdout,
496 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
497 self.assertIn(b'Provisioned on server: no', stdout,
498 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
499 self.assertIn(b'Fetched metadata: no', stdout,
500 'Device already provisioned!? ' + stderr.decode() + stdout.decode())
501
502 # Verify that HSM is not yet initialized.
503 pkcs11_command = 'pkcs11-tool --module=/usr/lib/softhsm/libsofthsm2.so -O'
504 stdout, stderr, retcode = self.qemu_command(pkcs11_command)
505 self.assertNotEqual(retcode, 0, 'pkcs11-tool succeeded before initialization: ' +
506 stdout.decode() + stderr.decode())
507 softhsm2_command = 'softhsm2-util --show-slots'
508 stdout, stderr, retcode = self.qemu_command(softhsm2_command)
509 self.assertNotEqual(retcode, 0, 'softhsm2-tool succeeded before initialization: ' +
510 stdout.decode() + stderr.decode())
511
512 # Run cert_provider.
513 bb_vars = get_bb_vars(['SOTA_PACKED_CREDENTIALS'], 'aktualizr-native')
514 creds = bb_vars['SOTA_PACKED_CREDENTIALS']
515 bb_vars_prov = get_bb_vars(['STAGING_DIR_NATIVE', 'libdir'], 'aktualizr-hsm-prov')
516 config = bb_vars_prov['STAGING_DIR_NATIVE'] + bb_vars_prov['libdir'] + '/sota/sota_hsm_prov.toml'
517
518 akt_native_run(self, 'aktualizr-cert-provider -c {creds} -t root@localhost -p {port} -r -s -u -g {config}'
519 .format(creds=creds, port=self.qemu.ssh_port, config=config))
520
521 # Verify that HSM is able to initialize.
522 ran_ok = False
523 for delay in [5, 5, 5, 5, 10]:
524 sleep(delay)
525 p11_out, p11_err, p11_ret = self.qemu_command(pkcs11_command)
526 hsm_out, hsm_err, hsm_ret = self.qemu_command(softhsm2_command)
527 if p11_ret == 0 and hsm_ret == 0 and hsm_err == b'':
528 ran_ok = True
529 break
530 self.assertTrue(ran_ok, 'pkcs11-tool or softhsm2-tool failed: ' + p11_err.decode() +
531 p11_out.decode() + hsm_err.decode() + hsm_out.decode())
532 self.assertIn(b'present token', p11_err, 'pkcs11-tool failed: ' + p11_err.decode() + p11_out.decode())
533 self.assertIn(b'X.509 cert', p11_out, 'pkcs11-tool failed: ' + p11_err.decode() + p11_out.decode())
534 self.assertIn(b'Initialized: yes', hsm_out, 'softhsm2-tool failed: ' +
535 hsm_err.decode() + hsm_out.decode())
536 self.assertIn(b'User PIN init.: yes', hsm_out, 'softhsm2-tool failed: ' +
537 hsm_err.decode() + hsm_out.decode())
538
539 # Check that pkcs11 output matches sofhsm output.
540 p11_p = re.compile(r'Using slot [0-9] with a present token \((0x[0-9a-f]*)\)\s')
541 p11_m = p11_p.search(p11_err.decode())
542 self.assertTrue(p11_m, 'Slot number not found with pkcs11-tool: ' + p11_err.decode() + p11_out.decode())
543 self.assertGreater(p11_m.lastindex, 0, 'Slot number not found with pkcs11-tool: ' +
544 p11_err.decode() + p11_out.decode())
545 hsm_p = re.compile(r'Description:\s*SoftHSM slot ID (0x[0-9a-f]*)\s')
546 hsm_m = hsm_p.search(hsm_out.decode())
547 self.assertTrue(hsm_m, 'Slot number not found with softhsm2-tool: ' + hsm_err.decode() + hsm_out.decode())
548 self.assertGreater(hsm_m.lastindex, 0, 'Slot number not found with softhsm2-tool: ' +
549 hsm_err.decode() + hsm_out.decode())
550 self.assertEqual(p11_m.group(1), hsm_m.group(1), 'Slot number does not match: ' +
551 p11_err.decode() + p11_out.decode() + hsm_err.decode() + hsm_out.decode())
552
553 verifyProvisioned(self, machine)
554
555
556class SecondaryTests(oeSelfTest):
557 @classmethod
558 def setUpClass(cls):
559 super(SecondaryTests, cls).setUpClass()
560 logger = logging.getLogger("selftest")
561 logger.info('Running bitbake to build secondary-image')
562 bitbake('secondary-image')
563
564 def setUpLocal(self):
565 layer = "meta-updater-qemux86-64"
566 result = runCmd('bitbake-layers show-layers')
567 if re.search(layer, result.output) is None:
568 # Assume the directory layout for finding other layers. We could also
569 # make assumptions by using 'show-layers', but either way, if the
570 # layers we need aren't where we expect them, we are out of luck.
571 path = os.path.abspath(os.path.dirname(__file__))
572 metadir = path + "/../../../../../"
573 self.meta_qemu = metadir + layer
574 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
575 else:
576 self.meta_qemu = None
577 self.append_config('MACHINE = "qemux86-64"')
578 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
579 self.qemu, self.s = qemu_launch(machine='qemux86-64', imagename='secondary-image')
580
581 def tearDownLocal(self):
582 qemu_terminate(self.s)
583 if self.meta_qemu:
584 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
585
586 def qemu_command(self, command):
587 return qemu_send_command(self.qemu.ssh_port, command)
588
589 def test_secondary_present(self):
590 print('Checking aktualizr-secondary is present')
591 stdout, stderr, retcode = self.qemu_command('aktualizr-secondary --help')
592 self.assertEqual(retcode, 0, "Unable to run aktualizr-secondary --help")
593 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
594
595 def test_secondary_listening(self):
596 print('Checking aktualizr-secondary service is listening')
597 stdout, stderr, retcode = self.qemu_command('aktualizr-check-discovery')
598 self.assertEqual(retcode, 0, "Unable to connect to secondary")
599
600
601class PrimaryTests(oeSelfTest):
602 @classmethod
603 def setUpClass(cls):
604 super(PrimaryTests, cls).setUpClass()
605 logger = logging.getLogger("selftest")
606 logger.info('Running bitbake to build primary-image')
607 bitbake('primary-image')
608
609 def setUpLocal(self):
610 layer = "meta-updater-qemux86-64"
611 result = runCmd('bitbake-layers show-layers')
612 if re.search(layer, result.output) is None:
613 # Assume the directory layout for finding other layers. We could also
614 # make assumptions by using 'show-layers', but either way, if the
615 # layers we need aren't where we expect them, we are out of luck.
616 path = os.path.abspath(os.path.dirname(__file__))
617 metadir = path + "/../../../../"
618 self.meta_qemu = metadir + layer
619 runCmd('bitbake-layers add-layer "%s"' % self.meta_qemu)
620 else:
621 self.meta_qemu = None
622 self.append_config('MACHINE = "qemux86-64"')
623 self.append_config('SOTA_CLIENT_PROV = " aktualizr-auto-prov "')
624 self.append_config('SOTA_CLIENT_FEATURES = "secondary-network"')
625 self.qemu, self.s = qemu_launch(machine='qemux86-64', imagename='primary-image')
626
627 def tearDownLocal(self):
628 qemu_terminate(self.s)
629 if self.meta_qemu:
630 runCmd('bitbake-layers remove-layer "%s"' % self.meta_qemu, ignore_status=True)
631
632 def qemu_command(self, command):
633 return qemu_send_command(self.qemu.ssh_port, command)
634
635 def test_aktualizr_present(self):
636 print('Checking aktualizr is present')
637 stdout, stderr, retcode = self.qemu_command('aktualizr --help')
638 self.assertEqual(retcode, 0, "Unable to run aktualizr --help")
639 self.assertEqual(stderr, b'', 'Error: ' + stderr.decode())
640
641
642def qemu_launch(efi=False, machine=None, imagename=None):
643 logger = logging.getLogger("selftest")
644 logger.info('Running bitbake to build core-image-minimal')
645 bitbake('core-image-minimal')
646 # Create empty object.
647 args = type('', (), {})()
648 if imagename:
649 args.imagename = imagename
650 else:
651 args.imagename = 'core-image-minimal'
652 args.mac = None
653 # Could use DEPLOY_DIR_IMAGE here but it's already in the machine
654 # subdirectory.
655 args.dir = 'tmp/deploy/images'
656 args.efi = efi
657 args.machine = machine
658 args.kvm = None # Autodetect
659 args.no_gui = True
660 args.gdb = False
661 args.pcap = None
662 args.overlay = None
663 args.dry_run = False
664 args.secondary_network = False
665
666 qemu = QemuCommand(args)
667 cmdline = qemu.command_line()
668 print('Booting image with run-qemu-ota...')
669 s = subprocess.Popen(cmdline)
670 sleep(10)
671 return qemu, s
672
673
674def qemu_terminate(s):
675 try:
676 s.terminate()
677 except KeyboardInterrupt:
678 pass
679
680
681def qemu_send_command(port, command):
682 command = ['ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@localhost -p ' +
683 str(port) + ' "' + command + '"']
684 s2 = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
685 stdout, stderr = s2.communicate(timeout=60)
686 return stdout, stderr, s2.returncode
687
688
689def akt_native_run(testInst, cmd, **kwargs):
690 # run a command supplied by aktualizr-native and checks that:
691 # - the executable exists
692 # - the command runs without error
693 # NOTE: the base test class must have built aktualizr-native (in
694 # setUpClass, for example)
695 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'base_prefix', 'libdir', 'bindir'],
696 'aktualizr-native')
697 sysroot = bb_vars['SYSROOT_DESTDIR'] + bb_vars['base_prefix']
698 sysrootbin = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir']
699 libdir = bb_vars['libdir']
700
701 program, *_ = cmd.split(' ')
702 p = '{}/{}'.format(sysrootbin, program)
703 testInst.assertTrue(os.path.isfile(p), msg="No {} found ({})".format(program, p))
704 env = dict(os.environ)
705 env['LD_LIBRARY_PATH'] = libdir
706 #result = runCmd(cmd, env=env, native_sysroot=sysroot, ignore_status=True, **kwargs)
707 result = runCmd(cmd, env=env, ignore_status=True, **kwargs)
708 testInst.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
709
710
711def verifyProvisioned(testInst, machine):
712 # Verify that device HAS provisioned.
713 ran_ok = False
714 for delay in [5, 5, 5, 5, 10, 10, 10, 10]:
715 stdout, stderr, retcode = testInst.qemu_command('aktualizr-info')
716 if retcode == 0 and stderr == b'' and stdout.decode().find('Fetched metadata: yes') >= 0:
717 ran_ok = True
718 break
719 sleep(delay)
720 testInst.assertIn(b'Device ID: ', stdout, 'Provisioning failed: ' + stderr.decode() + stdout.decode())
721 testInst.assertIn(b'Primary ecu hardware ID: ' + machine.encode(), stdout,
722 'Provisioning failed: ' + stderr.decode() + stdout.decode())
723 testInst.assertIn(b'Fetched metadata: yes', stdout, 'Provisioning failed: ' + stderr.decode() + stdout.decode())
724 p = re.compile(r'Device ID: ([a-z0-9-]*)\n')
725 m = p.search(stdout.decode())
726 testInst.assertTrue(m, 'Device ID could not be read: ' + stderr.decode() + stdout.decode())
727 testInst.assertGreater(m.lastindex, 0, 'Device ID could not be read: ' + stderr.decode() + stdout.decode())
728 logger = logging.getLogger("selftest")
729 logger.info('Device successfully provisioned with ID: ' + m.group(1))
730
731
732# vim:set ts=4 sw=4 sts=4 expandtab: