Skip to content

Commit

Permalink
Merge pull request #130 from Bastian-Krause/bst/http-streaming
Browse files Browse the repository at this point in the history
Add HTTP Streaming Support
ejoerns authored Oct 13, 2022
2 parents 89a91bd + 530dfc8 commit 12ace4e
Showing 16 changed files with 278 additions and 37 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -57,7 +57,9 @@ jobs:
- name: Update/launch hawkBit docker container
run: |
docker pull hawkbit/hawkbit-update-server
docker run -d --name hawkbit -p 8080:8080 hawkbit/hawkbit-update-server
docker run -d --name hawkbit -p 8080:8080 hawkbit/hawkbit-update-server \
--hawkbit.server.security.dos.filter.enabled=false \
--hawkbit.server.security.dos.maxStatusEntriesPerAction=-1
- name: Run test suite
run: |
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ Setup target (device) configuration file:
timeout = 60
log_level = debug
post_update_reboot = false
#enable_streaming = true

[device]
product = Terminator
@@ -98,7 +99,9 @@ Run hawkBit docker container:

```shell
$ docker pull hawkbit/hawkbit-update-server
$ docker run -d --name hawkbit -p 8080:8080 hawkbit/hawkbit-update-server
$ docker run -d --name hawkbit -p 8080:8080 hawkbit/hawkbit-update-server \
--hawkbit.server.security.dos.filter.enabled=false \
--hawkbit.server.security.dos.maxStatusEntriesPerAction=-1
```

Run test suite:
12 changes: 12 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
@@ -65,6 +65,8 @@ Mandatory options:
E.g. set to ``/tmp/_bundle.raucb`` to let rauc-hawkbit-updater use this
location within ``/tmp``.

.. note:: Option can be ommited if ``stream_bundle`` is enabled.

Optional options:

``tenant_id=<ID>``
@@ -81,6 +83,7 @@ Optional options:
``connect_timeout=<seconds>``
HTTP connection setup timeout [seconds].
Defaults to ``20`` seconds.
Has no effect on bundle downloading when used with ``stream_bundle=true``.

``timeout=<seconds>``
HTTP request timeout [seconds].
@@ -94,16 +97,25 @@ Optional options:
Time to be below ``low_speed_rate`` to trigger the low speed abort.
Defaults to ``60``.
See https://curl.se/libcurl/c/CURLOPT_LOW_SPEED_TIME.html.
Has no effect when used with ``stream_bundle=true``.

``low_speed_rate=<bytes per second>``
Average transfer speed to be below during ``low_speed_time`` seconds to
consider transfer as "too slow" and abort it.
Defaults to ``100``.
See https://curl.se/libcurl/c/CURLOPT_LOW_SPEED_LIMIT.html.
Has no effect when used with ``stream_bundle=true``.

``resume_downloads=<boolean>``
Whether to resume aborted downloads or not.
Defaults to ``false``.
Has no effect when used with ``stream_bundle=true``.

``stream_bundle=<boolean>``
Whether to install bundles via
`RAUC's HTTP streaming installation support <https://rauc.readthedocs.io/en/latest/advanced.html#http-streaming>`_.
rauc-hawkbit-updater does not download the bundle in this case, but rather
hands the hawkBit bundle URL and the :ref:`authentication header <authentication-section>` to RAUC.

``post_update_reboot=<boolean>``
Whether to reboot the system after a successful update.
16 changes: 16 additions & 0 deletions docs/using.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Using the RAUC hawkbit Updater
==============================

.. _authentication-section:

Authentication
--------------

@@ -22,6 +24,20 @@ Although gateway token is very handy during development or testing, it's
recommended to use this token with care because it can be used to
authenticate any device.

Streaming Support
-----------------

By default, rauc-hawkbit-updater downloads the bundle to a temporary
storage location and then invokes RAUC to install the bundle.
In order to save bundle storage and also potentially download bandwidth
(when combined with adaptive updates), rauc-hawkbit-updater can also leverage
`RAUC's built-in HTTP streaming support <https://rauc.readthedocs.io/en/latest/advanced.html#http-streaming>`_.

To enable it, set ``stream_bundle=true`` in the :ref:`sec_ref_config_file`.

.. note:: rauc-hawkbit-updater will add required authentication headers and
options to its RAUC D-Bus `InstallBundle API call <https://rauc.readthedocs.io/en/latest/reference.html#gdbus-method-de-pengutronix-rauc-installer-installbundle>`_.

Plain Bundle Support
--------------------

1 change: 1 addition & 0 deletions include/config-file.h
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ typedef struct Config_ {
gboolean ssl_verify; /**< verify https certificate */
gboolean post_update_reboot; /**< reboot system after successful update */
gboolean resume_downloads; /**< resume downloads or not */
gboolean stream_bundle; /**< streaming installation or not */
gchar* auth_token; /**< hawkBit target security token */
gchar* gateway_token; /**< hawkBit gateway security token */
gchar* tenant_id; /**< hawkBit tenant id */
3 changes: 3 additions & 0 deletions include/hawkbit-client.h
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ typedef enum {
RHU_HAWKBIT_CLIENT_ERROR_MULTI_CHUNKS,
RHU_HAWKBIT_CLIENT_ERROR_MULTI_ARTIFACTS,
RHU_HAWKBIT_CLIENT_ERROR_DOWNLOAD,
RHU_HAWKBIT_CLIENT_ERROR_STREAM_INSTALL,
RHU_HAWKBIT_CLIENT_ERROR_CANCELATION,
} RHUHawkbitClientError;

@@ -99,6 +100,8 @@ struct on_new_software_userdata {
GSourceFunc install_progress_callback; /**< callback function to be called when new progress */
GSourceFunc install_complete_callback; /**< callback function to be called when installation is complete */
gchar *file; /**< downloaded new software file */
gchar *auth_header; /**< authentication header for bundle streaming */
gboolean ssl_verify; /**< whether to ignore server cert verification errors */
gboolean install_success; /**< whether the installation succeeded or not (only meaningful for run_once mode!) */
};

9 changes: 7 additions & 2 deletions include/rauc-installer.h
Original file line number Diff line number Diff line change
@@ -13,6 +13,8 @@
*/
struct install_context {
gchar *bundle; /**< Rauc bundle file to install */
gchar *auth_header; /**< Authentication header for bundle streaming */
gboolean ssl_verify; /**< Whether to ignore server cert verification errors */
GSourceFunc notify_event; /**< Callback function */
GSourceFunc notify_complete; /**< Callback function */
GMutex status_mutex; /**< Mutex used for accessing status_messages */
@@ -27,6 +29,9 @@ struct install_context {
* @brief RAUC install bundle
*
* @param[in] bundle RAUC bundle file (.raucb) to install.
* @param[in] auth_header Authentication header on HTTP streaming installation or NULL on normal
* installation.
* @param[in] ssl_verify Whether to ignore server cert verification errors.
* @param[in] on_install_notify Callback function to be called with status info during
* installation.
* @param[in] on_install_complete Callback function to be called with the result of the
@@ -35,7 +40,7 @@ struct install_context {
* @return for wait=TRUE, TRUE if installation succeeded, FALSE otherwise; for
* wait=FALSE TRUE is always returned immediately
*/
gboolean rauc_install(const gchar *bundle, GSourceFunc on_install_notify,
GSourceFunc on_install_complete, gboolean wait);
gboolean rauc_install(const gchar *bundle, const gchar *auth_header, gboolean ssl_verify,
GSourceFunc on_install_notify, GSourceFunc on_install_complete, gboolean wait);

#endif // __RAUC_INSTALLER_H__
15 changes: 12 additions & 3 deletions src/config-file.c
Original file line number Diff line number Diff line change
@@ -240,6 +240,7 @@ Config* load_config_file(const gchar *config_file, GError **error)
g_autoptr(GKeyFile) ini_file = NULL;
gboolean key_auth_token_exists = FALSE;
gboolean key_gateway_token_exists = FALSE;
gboolean bundle_location_given = FALSE;

g_return_val_if_fail(config_file, NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
@@ -274,9 +275,8 @@ Config* load_config_file(const gchar *config_file, GError **error)
return NULL;
if (!get_key_string(ini_file, "client", "tenant_id", &config->tenant_id, "DEFAULT", error))
return NULL;
if (!get_key_string(ini_file, "client", "bundle_download_location",
&config->bundle_download_location, NULL, error))
return NULL;
bundle_location_given = get_key_string(ini_file, "client", "bundle_download_location",
&config->bundle_download_location, NULL, NULL);
if (!get_key_bool(ini_file, "client", "ssl", &config->ssl, DEFAULT_SSL, error))
return NULL;
if (!get_key_bool(ini_file, "client", "ssl_verify", &config->ssl_verify,
@@ -300,6 +300,9 @@ Config* load_config_file(const gchar *config_file, GError **error)
if (!get_key_bool(ini_file, "client", "resume_downloads", &config->resume_downloads, FALSE,
error))
return NULL;
if (!get_key_bool(ini_file, "client", "stream_bundle", &config->stream_bundle, FALSE,
error))
return NULL;
if (!get_key_string(ini_file, "client", "log_level", &val, DEFAULT_LOG_LEVEL, error))
return NULL;
config->log_level = log_level_from_string(val);
@@ -316,6 +319,12 @@ Config* load_config_file(const gchar *config_file, GError **error)
return NULL;
}

if (!bundle_location_given && !config->stream_bundle) {
g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND,
"'bundle_download_location' is required if 'stream_bundle' is disabled");
return NULL;
}

return g_steal_pointer(&config);
}

87 changes: 81 additions & 6 deletions src/hawkbit-client.c
Original file line number Diff line number Diff line change
@@ -201,6 +201,25 @@ static gboolean add_curl_header(struct curl_slist **headers, const char *string,
return TRUE;
}

/**
* @brief Returns the header required to authenticate against hawkBit, either target or gateway
* token.
*
* @return header required to authenticate against hawkBit
*/
static char* get_auth_header()
{
if (hawkbit_config->auth_token)
return g_strdup_printf("Authorization: TargetToken %s",
hawkbit_config->auth_token);

if (hawkbit_config->gateway_token)
return g_strdup_printf("Authorization: GatewayToken %s",
hawkbit_config->gateway_token);

g_return_val_if_reached(NULL);
}

/**
* @brief Add hawkBit authorization header to Curl headers.
*
@@ -216,12 +235,7 @@ static gboolean set_auth_curl_header(struct curl_slist **headers, GError **error

g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

if (hawkbit_config->auth_token)
token = g_strdup_printf("Authorization: TargetToken %s",
hawkbit_config->auth_token);
else if (hawkbit_config->gateway_token)
token = g_strdup_printf("Authorization: GatewayToken %s",
hawkbit_config->gateway_token);
token = get_auth_header();
if (token)
res = add_curl_header(headers, token, error);

@@ -821,6 +835,8 @@ static gpointer download_thread(gpointer data)
.install_progress_callback = (GSourceFunc) hawkbit_progress,
.install_complete_callback = install_complete_cb,
.file = hawkbit_config->bundle_download_location,
.auth_header = NULL,
.ssl_verify = hawkbit_config->ssl_verify,
.install_success = FALSE,
};
g_autoptr(GError) error = NULL, feedback_error = NULL;
@@ -943,6 +959,61 @@ static gpointer download_thread(gpointer data)
return GINT_TO_POINTER(FALSE);
}

/**
* @brief Start a RAUC HTTP streaming installation without prior bundle download.
* This skips the download thread and starts the install thread (via rauc_install()
* directly).
* Must be called under locked active_action->mutex.
*
* @param[in] artifcat Artifact* to install
* @param[out] error Error
* @return TRUE if installation prcessing succeeded, FALSE otherwise (error set).
* Note that this functions returns TRUE even for canceled/skipped installations.
*/
static gboolean start_streaming_installation(Artifact *artifact, GError **error)
{
g_autofree gchar *auth_header = get_auth_header();
struct on_new_software_userdata userdata = {
.install_progress_callback = (GSourceFunc) hawkbit_progress,
.install_complete_callback = install_complete_cb,
.file = artifact->download_url,
.auth_header = auth_header,
.ssl_verify = hawkbit_config->ssl_verify,
.install_success = FALSE,
};

// installation might already be canceled
if (active_action->state == ACTION_STATE_CANCEL_REQUESTED) {
active_action->state = ACTION_STATE_CANCELED;
g_cond_signal(&active_action->cond);
return TRUE;
}

// skip installation if hawkBit asked us to do so
if (!artifact->do_install) {
active_action->state = ACTION_STATE_NONE;
return TRUE;
}

active_action->state = ACTION_STATE_INSTALLING;
g_cond_signal(&active_action->cond);
g_mutex_unlock(&active_action->mutex);

software_ready_cb(&userdata);

g_mutex_lock(&active_action->mutex);

// in case of run_once, userdata.install_access is set and must be passed on
if (!userdata.install_success) {
g_set_error(error, RHU_HAWKBIT_CLIENT_ERROR,
RHU_HAWKBIT_CLIENT_ERROR_STREAM_INSTALL,
"Streaming installation failed");
return FALSE;
}

return TRUE;
}

/**
* @brief Process hawkBit deployment described by req_root.
* Must be called under locked active_action->mutex.
@@ -1107,6 +1178,10 @@ static gboolean process_deployment(JsonNode *req_root, GError **error)

artifact->feedback_url = g_steal_pointer(&feedback_url);

// stream_bundle path exits early
if (hawkbit_config->stream_bundle)
return start_streaming_installation(artifact, error);

// unref/free previous download thread by joining it
if (thread_download)
g_thread_join(thread_download);
7 changes: 5 additions & 2 deletions src/rauc-hawkbit-updater.c
Original file line number Diff line number Diff line change
@@ -91,7 +91,8 @@ static gboolean on_rauc_install_complete_cb(gpointer data)
}

/**
* @brief GSourceFunc callback for download thread, triggers RAUC installation.
* @brief GSourceFunc callback for download thread, or main thread in case of HTTP streaming
* installation. Triggers RAUC installation.
*
* @param[in] data on_new_software_userdata pointer
* @return G_SOURCE_REMOVE is always returned
@@ -104,7 +105,9 @@ static gboolean on_new_software_ready_cb(gpointer data)

notify_hawkbit_install_progress = userdata->install_progress_callback;
notify_hawkbit_install_complete = userdata->install_complete_callback;
userdata->install_success = rauc_install(userdata->file, on_rauc_install_progress_cb,
userdata->install_success = rauc_install(userdata->file, userdata->auth_header,
userdata->ssl_verify,
on_rauc_install_progress_cb,
on_rauc_install_complete_cb, run_once);

return G_SOURCE_REMOVE;
16 changes: 14 additions & 2 deletions src/rauc-installer.c
Original file line number Diff line number Diff line change
@@ -113,6 +113,7 @@ static void install_context_free(struct install_context *context)
return;

g_free(context->bundle);
g_free(context->auth_header);
g_mutex_clear(&context->status_mutex);

// make sure all pending events are processed
@@ -146,6 +147,14 @@ static gpointer install_loop_thread(gpointer data)
context = data;
g_main_context_push_thread_default(context->loop_context);

if (context->auth_header) {
gchar *headers[2] = {NULL, NULL};
headers[0] = context->auth_header;
g_variant_dict_insert(&args, "http-headers", "^as", headers);

g_variant_dict_insert(&args, "tls-no-verify", "b", !context->ssl_verify);
}

g_debug("Creating RAUC DBUS proxy");
r_installer_proxy = r_installer_proxy_new_for_bus_sync(
bus_type, G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
@@ -191,8 +200,9 @@ static gpointer install_loop_thread(gpointer data)
return NULL;
}

gboolean rauc_install(const gchar *bundle, GSourceFunc on_install_notify,
GSourceFunc on_install_complete, gboolean wait)
gboolean rauc_install(const gchar *bundle, const gchar *auth_header, gboolean ssl_verify,
GSourceFunc on_install_notify, GSourceFunc on_install_complete,
gboolean wait)
{
GMainContext *loop_context = NULL;
struct install_context *context = NULL;
@@ -202,6 +212,8 @@ gboolean rauc_install(const gchar *bundle, GSourceFunc on_install_notify,
loop_context = g_main_context_new();
context = install_context_new();
context->bundle = g_strdup(bundle);
context->auth_header = g_strdup(auth_header);
context->ssl_verify = ssl_verify;
context->notify_event = on_install_notify;
context->notify_complete = on_install_complete;
context->mainloop = g_main_loop_new(loop_context, FALSE);
1 change: 1 addition & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ def hawkbit(pytestconfig):
client.set_config('authentication.targettoken.enabled', True)
client.set_config('authentication.gatewaytoken.enabled', True)
client.set_config('authentication.gatewaytoken.key', uuid4().hex)
client.set_config('anonymous.download.enabled', False)

return client

54 changes: 51 additions & 3 deletions test/rauc_dbus_dummy.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@

from gi.repository import GLib
from pydbus.generic import signal
import requests


class Installer:
@@ -77,9 +78,13 @@ def mimic_install():
return False

print(f'installing {source}')

# check bundle checksum matches expected checksum (passed to constructor)
assert self._get_bundle_sha1(source) == self._get_bundle_sha1(self._bundle)
try:
self._check_install_requirements(source, args)
except Exception as e:
self.Completed(1)
self.LastError = f'Installation error: {e}'
self.Operation = 'idle'
raise

GLib.timeout_add_seconds(interval=1, function=mimic_install)

@@ -97,6 +102,49 @@ def _get_bundle_sha1(bundle):

return sha1.hexdigest()

@staticmethod
def _get_http_bundle_sha1(url, auth_header):
"""Download file from URL using HTTP range requests and compute its sha1 checksum."""
sha1 = hashlib.sha1()
headers = auth_header
range_size = 128 * 1024 # default squashfs block size

offset = 0
while True:
headers['Range'] = f'bytes={offset}-{offset + range_size - 1}'
r = requests.get(url, headers=headers)
try:
r.raise_for_status()
sha1.update(r.content)
except requests.HTTPError:
if r.status_code == 416: # range not satisfiable, assuming download completed
break
raise

offset += range_size

return sha1.hexdigest()

def _check_install_requirements(self, source, args):
"""
Check that required headers are set, bundle is accessible (HTTP or locally) and its
checksum matches.
"""
if 'http-headers' in args:
assert len(args['http-headers']) == 1

[auth_header] = args['http-headers']
key, value = auth_header.split(': ', maxsplit=1)
http_bundle_sha1 = self._get_http_bundle_sha1(source, {key: value})
assert http_bundle_sha1 == self._get_bundle_sha1(self._bundle)

# assume ssl_verify=false is set in test setup
assert args['tls-no-verify'] is True

else:
# check bundle checksum matches expected checksum
assert self._get_bundle_sha1(source) == self._get_bundle_sha1(self._bundle)

@property
def Operation(self):
return self._operation
10 changes: 10 additions & 0 deletions test/test_basics.py
Original file line number Diff line number Diff line change
@@ -114,6 +114,16 @@ def test_register_and_check_valid_auth_token(adjust_config, trailing_space):
assert 'MESSAGE: Checking for new software...' in out
assert err == ''

def test_register_and_check_no_download_location_no_streaming(adjust_config):
"""Test config without bundle_download_location and without stream_bundle."""
config = adjust_config(remove={'client': 'bundle_download_location'})
out, err, exitcode = run(f'rauc-hawkbit-updater -c "{config}" -r')

assert exitcode == 4
assert out == ''
assert err.strip() == \
"Loading config file failed: 'bundle_download_location' is required if 'stream_bundle' is disabled"

def test_identify(hawkbit, config):
"""
Test that supplying target meta information works and information are received correctly by
18 changes: 15 additions & 3 deletions test/test_cancel.py
Original file line number Diff line number Diff line change
@@ -2,16 +2,21 @@
# SPDX-FileCopyrightText: 2021 Bastian Krause <bst@pengutronix.de>, Pengutronix

from pexpect import TIMEOUT, EOF
import pytest

from helper import run, run_pexpect

def test_cancel_before_poll(hawkbit, config, bundle_assigned, rauc_dbus_install_success):
@pytest.mark.parametrize('mode', ('download', 'streaming'))
def test_cancel_before_poll(hawkbit, adjust_config, bundle_assigned, rauc_dbus_install_success,
mode):
"""
Assign distribution containing bundle to target and cancel it right away. Then run
rauc-hawkbit-updater and make sure it acknowledges the not yet processed action.
"""
hawkbit.cancel_action()

config_params = {'client': {'stream_bundle': 'true'}} if mode == 'streaming' else {}
config = adjust_config(config_params)
out, err, exitcode = run(f'rauc-hawkbit-updater -c "{config}" -r')

assert f'Received cancelation for unprocessed action {hawkbit.id["action"]}, acknowledging.' \
@@ -35,7 +40,9 @@ def test_cancel_during_download(hawkbit, adjust_config, bundle_assigned, rate_li
started and make sure the cancelation is acknowledged and no installation is started.
"""
port = rate_limited_port('70k')
config = adjust_config({'client': {'hawkbit_server': f'{hawkbit.host}:{port}'}})

config_params = {'client': {'hawkbit_server': f'{hawkbit.host}:{port}'}}
config = adjust_config(config_params)

# note: we cannot use -r here since that prevents further polling of the base resource
# announcing the cancelation
@@ -65,12 +72,17 @@ def test_cancel_during_download(hawkbit, adjust_config, bundle_assigned, rate_li
assert cancel_status[0]['type'] == 'canceled'
assert 'Action canceled.' in cancel_status[0]['messages']

def test_cancel_during_install(hawkbit, config, bundle_assigned, rauc_dbus_install_success):
@pytest.mark.parametrize('mode', ('download', 'streaming'))
def test_cancel_during_install(hawkbit, adjust_config, bundle_assigned, rauc_dbus_install_success,
mode):
"""
Assign distribution containing bundle to target. Run rauc-hawkbit-updater and cancel the
assignment once the installation started. Make sure the cancelation does not disrupt the
installation.
"""
config_params = {'client': {'stream_bundle': 'true'}} if mode == 'streaming' else {}
config = adjust_config(config_params)

proc = run_pexpect(f'rauc-hawkbit-updater -c "{config}"')
proc.expect('MESSAGE: Installing: ')

57 changes: 43 additions & 14 deletions test/test_install.py
Original file line number Diff line number Diff line change
@@ -5,59 +5,81 @@
from pathlib import Path

from pexpect import TIMEOUT, EOF
import pytest

from helper import run, run_pexpect, timezone_offset_utc

def test_install_bundle_no_dbus_iface(hawkbit, bundle_assigned, config):
@pytest.mark.parametrize('mode', ('download', 'streaming'))
def test_install_bundle_no_dbus_iface(hawkbit, bundle_assigned, adjust_config, mode):
"""Assign bundle to target and test installation without RAUC D-Bus interface available."""
config_params = {'client': {'stream_bundle': 'true'}} if mode == 'streaming' else {}
config = adjust_config(config_params)

out, err, exitcode = run(f'rauc-hawkbit-updater -c "{config}" -r')

err_lines = err.splitlines()

assert 'New software ready for download' in out
assert 'Download complete' in out

if mode == 'download':
assert 'Download complete' in out

assert err_lines.pop(0) == \
'WARNING: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name de.pengutronix.rauc was not provided by any .service files'
assert err_lines.pop(0) == 'WARNING: Failed to install software bundle.'

if mode == 'streaming':
assert err_lines.pop(0) == 'WARNING: Streaming installation failed'

assert not err_lines
assert exitcode == 1

status = hawkbit.get_action_status()
assert status[0]['type'] == 'error'

def test_install_success(hawkbit, config, bundle_assigned, rauc_dbus_install_success):
@pytest.mark.parametrize('mode', ('download', 'streaming'))
def test_install_success(hawkbit, adjust_config, bundle_assigned, rauc_dbus_install_success, mode):
"""
Assign bundle to target and test successful download and installation. Make sure installation
result is received correctly by hawkBit.
"""
config_params = {'client': {'stream_bundle': 'true'}} if mode == 'streaming' else {}
config = adjust_config(config_params)
out, err, exitcode = run(f'rauc-hawkbit-updater -c "{config}" -r')

assert 'New software ready for download' in out
assert 'Download complete' in out

if mode == 'download':
assert 'Download complete' in out

assert 'Software bundle installed successfully.' in out
assert err == ''
assert exitcode == 0

status = hawkbit.get_action_status()
assert status[0]['type'] == 'finished'

def test_install_failure(hawkbit, config, bundle_assigned, rauc_dbus_install_failure):
@pytest.mark.parametrize('mode', ('download', 'streaming'))
def test_install_failure(hawkbit, adjust_config, bundle_assigned, rauc_dbus_install_failure, mode):
"""
Assign bundle to target and test successful download and failing installation. Make sure
installation result is received correctly by hawkBit.
"""
config_params = {'client': {'stream_bundle': 'true'}} if mode == 'streaming' else {}
config = adjust_config(config_params)
out, err, exitcode = run(f'rauc-hawkbit-updater -c "{config}" -r')

assert 'New software ready for download' in out
assert err.strip() == 'WARNING: Failed to install software bundle.'
assert 'WARNING: Failed to install software bundle.' in err
assert exitcode == 1

status = hawkbit.get_action_status()
assert status[0]['type'] == 'error'
assert 'Failed to install software bundle.' in status[0]['messages']

def test_install_maintenance_window(hawkbit, config, rauc_bundle, assign_bundle,
rauc_dbus_install_success):
@pytest.mark.parametrize('mode', ('download', 'streaming'))
def test_install_maintenance_window(hawkbit, adjust_config, rauc_bundle, assign_bundle,
rauc_dbus_install_success, mode):
bundle_size = Path(rauc_bundle).stat().st_size
maintenance_start = datetime.now() + timedelta(seconds=15)
maintenance_window = {
@@ -69,19 +91,26 @@ def test_install_maintenance_window(hawkbit, config, rauc_bundle, assign_bundle,
}
assign_bundle(params=maintenance_window)

config_params = {'client': {'stream_bundle': 'true'}} if mode == 'streaming' else {}
config = adjust_config(config_params)

proc = run_pexpect(f'rauc-hawkbit-updater -c "{config}"')
proc.expect(r"hawkBit requested to skip installation, not invoking RAUC yet \(maintenance window is 'unavailable'\)")
proc.expect('Start downloading')
proc.expect('Download complete')
proc.expect('File checksum OK')

if mode == 'download':
proc.expect('Start downloading')
proc.expect('Download complete')
proc.expect('File checksum OK')

# wait for the maintenance window to become available and the next poll of the base resource
proc.expect(TIMEOUT, timeout=30)
proc.expect(r"Continuing scheduled deployment .* \(maintenance window is 'available'\)")
# RAUC bundle should have been already downloaded completely
proc.expect(f'Resuming download from offset {bundle_size}')
proc.expect('Download complete')
proc.expect('File checksum OK')
if mode == 'download':
proc.expect(f'Resuming download from offset {bundle_size}')
proc.expect('Download complete')
proc.expect('File checksum OK')

proc.expect('Software bundle installed successfully')

# let feedback propagate to hawkBit before termination

0 comments on commit 12ace4e

Please sign in to comment.