Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
ipsecondary_test.py
1 #!/usr/bin/env python3
2 
3 import argparse
4 import logging
5 import time
6 
7 from os import getcwd, chdir, path
8 
9 from test_fixtures import with_aktualizr, with_uptane_backend, KeyStore, with_secondary, with_treehub,\
10  with_sysroot, with_director, TestRunner, IPSecondary
11 
12 logger = logging.getLogger("IPSecondaryTest")
13 
14 
15 # The following is a test suit intended for IP Secondary integration testing
16 @with_uptane_backend()
17 @with_secondary(start=True)
18 @with_aktualizr(start=False, output_logs=False)
19 def test_secondary_update_if_secondary_starts_first(uptane_repo, secondary, aktualizr, **kwargs):
20  '''Test Secondary update if Secondary is booted before Primary'''
21 
22  # add a new image to the repo in order to update the Secondary with it
23  secondary_image_filename = "secondary_image_filename_001.img"
24  secondary_image_hash = uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
25 
26  logger.debug("Trying to update ECU {} with the image {}".
27  format(secondary.id, (secondary_image_hash, secondary_image_filename)))
28 
29  with aktualizr:
30  # run aktualizr once, Secondary has been already running
31  aktualizr.wait_for_completion()
32 
33  test_result = secondary_image_hash == aktualizr.get_current_image_info(secondary.id)
34  logger.debug("Update result: {}".format("success" if test_result else "failed"))
35  return test_result
36 
37 
38 @with_uptane_backend()
39 @with_secondary(start=False)
40 @with_aktualizr(start=True, output_logs=False)
41 def test_secondary_update_if_primary_starts_first(uptane_repo, secondary, aktualizr, **kwargs):
42  '''Test Secondary update if Secondary is booted after Primary'''
43 
44  # add a new image to the repo in order to update the Secondary with it
45  secondary_image_filename = "secondary_image_filename_001.img"
46  secondary_image_hash = uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
47 
48  logger.debug("Trying to update ECU {} with the image {}".
49  format(secondary.id, (secondary_image_hash, secondary_image_filename)))
50  with secondary:
51  # start Secondary, aktualizr has been already started in 'once' mode
52  aktualizr.wait_for_completion()
53 
54  test_result = secondary_image_hash == aktualizr.get_current_image_info(secondary.id)
55  logger.debug("Update result: {}".format("success" if test_result else "failed"))
56  return test_result
57 
58 
59 @with_uptane_backend()
60 @with_director()
61 @with_secondary(start=False, output_logs=True)
62 @with_aktualizr(start=False, output_logs=True)
63 def test_secondary_update(uptane_repo, secondary, aktualizr, director, **kwargs):
64  '''Test Secondary update if a boot order of Secondary and Primary is undefined'''
65 
66  # add a new image to the repo in order to update the Secondary with it
67  secondary_image_filename = "secondary_image_filename.img"
68  secondary_image_hash = uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
69 
70  logger.debug("Trying to update ECU {} with the image {}".
71  format(secondary.id, (secondary_image_hash, secondary_image_filename)))
72 
73  # start Secondary and aktualizr processes, aktualizr is started in 'once' mode
74  with secondary, aktualizr:
75  aktualizr.wait_for_completion()
76 
77  if not director.get_install_result():
78  logger.error("Installation result is not successful")
79  return False
80 
81  # check currently installed hash
82  if secondary_image_hash != aktualizr.get_current_image_info(secondary.id):
83  logger.error("Target image hash doesn't match the currently installed hash")
84  return False
85 
86  # check updated file
87  update_file = path.join(secondary.storage_dir.name, "firmware.txt")
88  if not path.exists(update_file):
89  logger.error("Expected updated file does not exist: {}".format(update_file))
90  return False
91 
92  if secondary_image_filename != director.get_ecu_manifest_filepath(secondary.id[1]):
93  logger.error("Target name doesn't match a filepath value of the reported manifest: {}".format(director.get_manifest()))
94  return False
95 
96  return True
97 
98 
99 @with_uptane_backend()
100 @with_secondary(start=True, id=('hwid1', 'serial1'), output_logs=False)
101 @with_aktualizr(start=False, output_logs=True)
102 def test_add_secondary(uptane_repo, secondary, aktualizr, **kwargs):
103  '''Test adding a Secondary after registration'''
104 
105  with aktualizr:
106  aktualizr.wait_for_completion()
107 
108  if not aktualizr.is_ecu_registered(secondary.id):
109  logger.error("Secondary ECU is not registered.")
110  return False
111 
112  with IPSecondary(output_logs=False, id=('hwid1', 'serial2')) as secondary2:
113  # Why is this necessary? The Primary waiting works outside of this test.
114  time.sleep(5)
115  aktualizr.add_secondary(secondary2)
116  with aktualizr:
117  aktualizr.wait_for_completion()
118 
119  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary2.id):
120  logger.error("Secondary ECU is not registered.")
121  return False
122 
123  return True
124 
125 
126 @with_uptane_backend()
127 @with_secondary(start=True, id=('hwid1', 'serial1'), output_logs=False)
128 @with_secondary(start=True, id=('hwid1', 'serial2'), output_logs=False, arg_name='secondary2')
129 @with_aktualizr(start=False, output_logs=True)
130 def test_remove_secondary(uptane_repo, secondary, secondary2, aktualizr, **kwargs):
131  '''Test removing a Secondary after registration'''
132 
133  with aktualizr:
134  aktualizr.wait_for_completion()
135 
136  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary2.id):
137  logger.error("Secondary ECU is not registered.")
138  return False
139 
140  aktualizr.remove_secondary(secondary2)
141  with aktualizr:
142  aktualizr.wait_for_completion()
143 
144  if not aktualizr.is_ecu_registered(secondary.id):
145  logger.error("Secondary ECU is not registered.")
146  return False
147  if aktualizr.is_ecu_registered(secondary2.id):
148  logger.error("Secondary ECU is unexpectedly still registered.")
149  return False
150 
151  return True
152 
153 
154 @with_uptane_backend()
155 @with_secondary(start=True, id=('hwid1', 'serial1'), output_logs=False)
156 @with_secondary(start=True, id=('hwid1', 'serial2'), output_logs=False, arg_name='secondary2')
157 @with_aktualizr(start=False, output_logs=True)
158 def test_replace_secondary(uptane_repo, secondary, secondary2, aktualizr, **kwargs):
159  '''Test replacing a Secondary after registration'''
160 
161  with aktualizr:
162  aktualizr.wait_for_completion()
163 
164  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary2.id):
165  logger.error("Secondary ECU is not registered.")
166  return False
167 
168  aktualizr.remove_secondary(secondary2)
169 
170  with IPSecondary(output_logs=False, id=('hwid1', 'serial3')) as secondary3:
171  # Why is this necessary? The Primary waiting works outside of this test.
172  time.sleep(5)
173  aktualizr.add_secondary(secondary3)
174  with aktualizr:
175  aktualizr.wait_for_completion()
176 
177  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary3.id):
178  logger.error("Secondary ECU is not registered.")
179  return False
180  if aktualizr.is_ecu_registered(secondary2.id):
181  logger.error("Secondary ECU is unexpectedly still registered.")
182  return False
183 
184  return True
185 
186 
187 @with_uptane_backend()
188 @with_secondary(start=True, id=('hwid1', 'serial1'), output_logs=False)
189 @with_aktualizr(start=False, output_logs=True)
190 def test_replace_secondary_same_port(uptane_repo, secondary, aktualizr, **kwargs):
191  '''Test replacing a Secondary that reuses the same port'''
192 
193  port = IPSecondary.get_free_port()
194  with IPSecondary(output_logs=False, id=('hwid1', 'serial2'), port=port) as secondary2:
195  # Why is this necessary? The Primary waiting works outside of this test.
196  time.sleep(5)
197  aktualizr.add_secondary(secondary2)
198  with aktualizr:
199  aktualizr.wait_for_completion()
200 
201  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary2.id):
202  logger.error("Secondary ECU is not registered.")
203  return False
204 
205  aktualizr.remove_secondary(secondary2)
206 
207  with IPSecondary(output_logs=False, id=('hwid1', 'serial3'), port=port) as secondary3:
208  # Why is this necessary? The Primary waiting works outside of this test.
209  time.sleep(5)
210  aktualizr.add_secondary(secondary3)
211  with aktualizr:
212  aktualizr.wait_for_completion()
213 
214  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary3.id):
215  logger.error("Secondary ECU is not registered.")
216  return False
217  if aktualizr.is_ecu_registered(secondary2.id):
218  logger.error("Secondary ECU is unexpectedly still registered.")
219  return False
220 
221  return True
222 
223 
224 @with_uptane_backend()
225 @with_secondary(start=True, id=('hwid1', 'serial1'), output_logs=False)
226 @with_aktualizr(start=False, output_logs=True)
227 def test_change_secondary_port(uptane_repo, secondary, aktualizr, **kwargs):
228  '''Test changing a Secondary's port but not the ECU serial'''
229 
230  with IPSecondary(output_logs=False, id=('hwid1', 'serial2')) as secondary2:
231  # Why is this necessary? The Primary waiting works outside of this test.
232  time.sleep(5)
233  aktualizr.add_secondary(secondary2)
234  with aktualizr:
235  aktualizr.wait_for_completion()
236 
237  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary2.id):
238  logger.error("Secondary ECU is not registered.")
239  return False
240 
241  aktualizr.remove_secondary(secondary2)
242 
243  with IPSecondary(output_logs=False, id=('hwid1', 'serial2')) as secondary3:
244  # Why is this necessary? The Primary waiting works outside of this test.
245  time.sleep(5)
246  aktualizr.add_secondary(secondary3)
247  with aktualizr:
248  aktualizr.wait_for_completion()
249 
250  if secondary2.port == secondary3.port:
251  logger.error("Secondary ECU port unexpectedly did not change!")
252  return False
253 
254  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary3.id):
255  logger.error("Secondary ECU is not registered.")
256  return False
257 
258  return True
259 
260 
261 @with_treehub()
262 @with_uptane_backend()
263 @with_director()
264 @with_sysroot()
265 @with_secondary(start=False, output_logs=True)
266 @with_aktualizr(start=False, run_mode='once', output_logs=True)
267 def test_secondary_ostree_update(uptane_repo, secondary, aktualizr, treehub, sysroot, director, **kwargs):
268  """Test Secondary OSTree update if a boot order of Secondary and Primary is undefined"""
269 
270  target_rev = treehub.revision
271  expected_targetname = uptane_repo.add_ostree_target(secondary.id, target_rev, "GARAGE_TARGET_NAME")
272 
273  with secondary:
274  with aktualizr:
275  aktualizr.wait_for_completion()
276 
277  pending_rev = aktualizr.get_current_pending_image_info(secondary.id)
278 
279  if pending_rev != target_rev:
280  logger.error("Pending version {} != the target one {}".format(pending_rev, target_rev))
281  return False
282 
283  sysroot.update_revision(pending_rev)
284  secondary.emulate_reboot()
285 
286  aktualizr.set_mode('full')
287  with aktualizr:
288  with secondary:
289  director.wait_for_install()
290 
291  if not director.get_install_result():
292  logger.error("Installation result is not successful")
293  return False
294 
295  installed_rev = aktualizr.get_current_image_info(secondary.id)
296 
297  if installed_rev != target_rev:
298  logger.error("Installed version {} != the target one {}".format(installed_rev, target_rev))
299  return False
300 
301  if expected_targetname != director.get_ecu_manifest_filepath(secondary.id[1]):
302  logger.error(
303  "Target name doesn't match a filepath value of the reported manifest: expected: {}, actual: {}".
304  format(expected_targetname, director.get_ecu_manifest_filepath(secondary.id[1])))
305  return False
306 
307  return True
308 
309 
310 @with_treehub()
311 @with_uptane_backend()
312 @with_director()
313 @with_sysroot()
314 @with_secondary(start=False, output_logs=False, force_reboot=True)
315 @with_aktualizr(start=False, run_mode='once', output_logs=True)
316 def test_secondary_ostree_reboot(uptane_repo, secondary, aktualizr, treehub, sysroot, director, **kwargs):
317  target_rev = treehub.revision
318  uptane_repo.add_ostree_target(secondary.id, target_rev, "GARAGE_TARGET_NAME")
319 
320  with secondary:
321  with aktualizr:
322  aktualizr.wait_for_completion()
323  secondary.wait_for_completion()
324 
325  pending_rev = aktualizr.get_current_pending_image_info(secondary.id)
326 
327  if pending_rev != target_rev:
328  logger.error("Pending version {} != the target one {}".format(pending_rev, target_rev))
329  return False
330 
331  sysroot.update_revision(pending_rev)
332 
333  aktualizr.set_mode('full')
334  with secondary:
335  # Why is this necessary? The Primary waiting works outside of this test.
336  time.sleep(5)
337  with aktualizr:
338  director.wait_for_install()
339 
340  if not director.get_install_result():
341  logger.error("Installation result is not successful")
342  return False
343 
344  installed_rev = aktualizr.get_current_image_info(secondary.id)
345 
346  if installed_rev != target_rev:
347  logger.error("Installed version {} != the target one {}".format(installed_rev, target_rev))
348  return False
349 
350  return True
351 
352 
353 @with_uptane_backend()
354 @with_director()
355 @with_secondary(start=False)
356 @with_aktualizr(start=False, secondary_wait_sec=1, output_logs=False)
357 def test_secondary_install_timeout(uptane_repo, secondary, aktualizr, director, **kwargs):
358  '''Test that secondary install fails after a timeout if the secondary never connects'''
359 
360  # run aktualizr and secondary and wait until the device/aktualizr is registered
361  with aktualizr, secondary:
362  aktualizr.wait_for_completion()
363 
364  # the secondary must be registered
365  if not aktualizr.is_ecu_registered(secondary.id):
366  return False
367 
368  # make sure that the secondary is not running
369  if secondary.is_running():
370  return False
371 
372  # launch an update on secondary without it
373  secondary_image_filename = "secondary_image_filename_001.img"
374  uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
375 
376  aktualizr.update_wait_timeout(0.1)
377  with aktualizr:
378  aktualizr.wait_for_completion()
379 
380  manifest = director.get_manifest()
381  result_code = manifest["signed"]["installation_report"]["report"]["result"]["code"]
382  if result_code != "INTERNAL_ERROR":
383  logger.error("Wrong result code {}".format(result_code))
384  return False
385 
386  return not director.get_install_result()
387 
388 
389 @with_uptane_backend()
390 @with_secondary(start=False)
391 @with_aktualizr(start=False, output_logs=False, wait_timeout=0.1)
392 def test_primary_timeout_during_first_run(uptane_repo, secondary, aktualizr, **kwargs):
393  """Test Aktualizr's timeout of waiting for Secondaries during initial boot"""
394 
395  secondary_image_filename = "secondary_image_filename_001.img"
396  uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
397 
398  logger.debug("Checking Aktualizr behaviour if it timeouts while waiting for a connection from the secondary")
399 
400  # just start the aktualizr and expect that it timeouts on waiting for a connection from the Secondary
401  # so the Secondary is not registered at the device and backend
402  with aktualizr:
403  aktualizr.wait_for_completion()
404 
405  info = aktualizr.get_info()
406  if info is None:
407  return False
408  not_provisioned = 'Provisioned on server: no' in info
409 
410  return not_provisioned and not aktualizr.is_ecu_registered(secondary.id)
411 
412 
413 @with_uptane_backend()
414 @with_director()
415 @with_secondary(start=False)
416 @with_aktualizr(start=False, output_logs=True)
417 def test_primary_wait_secondary_install(uptane_repo, secondary, aktualizr, director, **kwargs):
418  """Test that Primary waits for Secondary to connect before installing"""
419 
420  # provision device with a secondary
421  with secondary, aktualizr:
422  aktualizr.wait_for_completion()
423 
424  secondary_image_filename = "secondary_image_filename.img"
425  uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
426 
427  with aktualizr:
428  time.sleep(10)
429  with secondary:
430  aktualizr.wait_for_completion()
431 
432  if not director.get_install_result():
433  logger.error("Installation result is not successful")
434  return False
435 
436  return True
437 
438 
439 @with_uptane_backend()
440 @with_secondary(start=False, output_logs=False)
441 @with_aktualizr(start=False, output_logs=False)
442 def test_primary_timeout_after_device_is_registered(uptane_repo, secondary, aktualizr, **kwargs):
443  '''Test Aktualizr's timeout of waiting for Secondaries after the device/aktualizr was registered at the backend'''
444 
445  # run aktualizr and Secondary and wait until the device/aktualizr is registered
446  with aktualizr, secondary:
447  aktualizr.wait_for_completion()
448 
449  # the Secondary must be registered
450  if not aktualizr.is_ecu_registered(secondary.id):
451  return False
452 
453  # make sure that the Secondary is not running
454  if secondary.is_running():
455  return False
456 
457  # run just aktualizr, the previously registered Secondary is off
458  # and check if the Primary ECU is updatable if the Secondary is not connected
459  primary_image_filename = "primary_image_filename_001.img"
460  primary_image_hash = uptane_repo.add_image(id=aktualizr.id, image_filename=primary_image_filename)
461 
462  # if a new image for the not-connected Secondary is specified in the target
463  # then nothing is going to be updated, including the image intended for
464  # healthy Primary ECU
465  # secondary_image_filename = "secondary_image_filename_001.img"
466  # secondary_image_hash = uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
467 
468  aktualizr.update_wait_timeout(0.1)
469  with aktualizr:
470  aktualizr.wait_for_completion()
471 
472  return aktualizr.get_current_primary_image_info() == primary_image_hash
473 
474 
475 @with_uptane_backend()
476 @with_secondary(start=False)
477 @with_secondary(start=False, arg_name='secondary2')
478 @with_aktualizr(start=False, output_logs=True)
479 def test_primary_multiple_secondaries(uptane_repo, secondary, secondary2, aktualizr, **kwargs):
480  '''Test Aktualizr with multiple IP secondaries'''
481 
482  with aktualizr, secondary, secondary2:
483  aktualizr.wait_for_completion()
484 
485  if not aktualizr.is_ecu_registered(secondary.id) or not aktualizr.is_ecu_registered(secondary2.id):
486  return False
487 
488  return True
489  secondary_image_filename = "secondary_image_filename.img"
490  uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename)
491  uptane_repo.add_image(id=secondary2.id, image_filename=secondary_image_filename)
492 
493  with aktualizr:
494  time.sleep(10)
495  with secondary:
496  aktualizr.wait_for_completion()
497 
498  if not director.get_install_result():
499  logger.error("Installation result is not successful")
500  return False
501 
502  return True
503 
504 
505 # test suit runner
506 if __name__ == '__main__':
507  logging.basicConfig(level=logging.INFO)
508 
509  parser = argparse.ArgumentParser(description='Test IP Secondary')
510  parser.add_argument('-b', '--build-dir', help='build directory', default='build')
511  parser.add_argument('-s', '--src-dir', help='source directory', default='.')
512  parser.add_argument('-o', '--ostree', help='OSTree support', action='store_true')
513 
514  input_params = parser.parse_args()
515 
516  KeyStore.base_dir = path.abspath(input_params.src_dir)
517  initial_cwd = getcwd()
518  chdir(input_params.build_dir)
519 
520  test_suite = [
521  test_secondary_update,
522  test_secondary_update_if_secondary_starts_first,
523  test_secondary_update_if_primary_starts_first,
524  test_add_secondary,
525  test_remove_secondary,
526  test_replace_secondary,
527  test_replace_secondary_same_port,
528  test_change_secondary_port,
529  test_secondary_install_timeout,
530  test_primary_timeout_during_first_run,
531  test_primary_timeout_after_device_is_registered,
532  test_primary_multiple_secondaries,
533  ]
534 
535  if input_params.ostree:
536  test_suite += [
537  test_secondary_ostree_update,
538  test_secondary_ostree_reboot,
539  ]
540 
541  with TestRunner(test_suite) as runner:
542  test_suite_run_result = runner.run()
543 
544  chdir(initial_cwd)
545  exit(0 if test_suite_run_result else 1)