goosebit 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
Files changed (52) hide show
  1. goosebit/__init__.py +5 -2
  2. goosebit/api/__init__.py +1 -1
  3. goosebit/api/devices.py +59 -39
  4. goosebit/api/download.py +28 -14
  5. goosebit/api/firmware.py +40 -34
  6. goosebit/api/helper.py +30 -0
  7. goosebit/api/rollouts.py +64 -13
  8. goosebit/api/routes.py +14 -7
  9. goosebit/auth/__init__.py +14 -6
  10. goosebit/db.py +5 -0
  11. goosebit/models.py +110 -10
  12. goosebit/permissions.py +26 -20
  13. goosebit/realtime/__init__.py +1 -1
  14. goosebit/realtime/logs.py +3 -6
  15. goosebit/settings.py +4 -6
  16. goosebit/telemetry/__init__.py +28 -0
  17. goosebit/telemetry/prometheus.py +10 -0
  18. goosebit/ui/__init__.py +1 -1
  19. goosebit/ui/routes.py +33 -40
  20. goosebit/ui/static/js/devices.js +187 -250
  21. goosebit/ui/static/js/firmware.js +229 -92
  22. goosebit/ui/static/js/index.js +79 -90
  23. goosebit/ui/static/js/logs.js +14 -11
  24. goosebit/ui/static/js/rollouts.js +169 -27
  25. goosebit/ui/static/js/util.js +66 -0
  26. goosebit/ui/templates/devices.html +75 -51
  27. goosebit/ui/templates/firmware.html +149 -35
  28. goosebit/ui/templates/index.html +9 -26
  29. goosebit/ui/templates/login.html +58 -27
  30. goosebit/ui/templates/logs.html +15 -5
  31. goosebit/ui/templates/nav.html +77 -26
  32. goosebit/ui/templates/rollouts.html +62 -39
  33. goosebit/updater/__init__.py +1 -1
  34. goosebit/updater/controller/__init__.py +1 -1
  35. goosebit/updater/controller/v1/__init__.py +1 -1
  36. goosebit/updater/controller/v1/routes.py +53 -35
  37. goosebit/updater/manager.py +205 -103
  38. goosebit/updater/routes.py +4 -7
  39. goosebit/updates/__init__.py +70 -0
  40. goosebit/updates/swdesc.py +83 -0
  41. {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/METADATA +53 -3
  42. goosebit-0.1.2.dist-info/RECORD +51 -0
  43. goosebit/updater/download/__init__.py +0 -1
  44. goosebit/updater/download/routes.py +0 -6
  45. goosebit/updater/download/v1/__init__.py +0 -1
  46. goosebit/updater/download/v1/routes.py +0 -13
  47. goosebit/updater/misc.py +0 -57
  48. goosebit/updates/artifacts.py +0 -89
  49. goosebit/updates/version.py +0 -38
  50. goosebit-0.1.1.dist-info/RECORD +0 -53
  51. {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/LICENSE +0 -0
  52. {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/WHEEL +0 -0
@@ -1,37 +1,20 @@
1
- {% extends "nav.html" %}
2
- {% block content %}
1
+ {% extends "nav.html" %} {% block content %}
3
2
  <div class="container-fluid">
4
3
  <div class="row p-2 d-flex justify-content-center">
5
4
  <div class="col">
6
5
  <table id="device-table" class="table table-hover">
7
6
  <thead>
8
7
  <tr>
9
- <th>
10
- Name
11
- </th>
12
- <th>
13
- Up
14
- </th>
15
- <th>
16
- UUID
17
- </th>
18
- <th>
19
- Firmware
20
- </th>
21
- <th>
22
- Progress
23
- </th>
24
- <th>
25
- Last IP
26
- </th>
27
- <th>
28
- Last Seen
29
- </th>
8
+ <th>Name</th>
9
+ <th>Up</th>
10
+ <th>UUID</th>
11
+ <th>Firmware</th>
12
+ <th>Progress</th>
13
+ <th>Last IP</th>
14
+ <th>Last Seen</th>
30
15
  </tr>
31
16
  </thead>
32
- <tbody id="devices-list">
33
-
34
- </tbody>
17
+ <tbody id="devices-list"></tbody>
35
18
  </table>
36
19
  </div>
37
20
  </div>
@@ -1,34 +1,65 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Login</title>
7
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
8
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
9
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
- <link rel="icon" href="{{ url_for('static', path='favicon.svg') }}">
11
- </head>
12
- <body data-bs-theme="dark">
13
- <div class="container py-5 h-100">
14
- <div class="row d-flex justify-content-center align-items-center h-100">
15
- <div class="col-12 col-md-8 col-lg-6 col-xl-5">
16
- <div class="mb-md-5 mt-md-4 pb-5">
17
- <h2 class="fw-bold mb-3 text-uppercase">Login</h2>
18
- <form method="post">
19
- <div class="form-outline form-white mb-4">
20
- <input type="email" id="username" name="username" placeholder="Email" class="form-control form-control-lg" />
21
- </div>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Login</title>
7
+ <link
8
+ href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
9
+ rel="stylesheet"
10
+ integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
11
+ crossorigin="anonymous"
12
+ />
13
+ <script
14
+ src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
15
+ integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
16
+ crossorigin="anonymous"
17
+ ></script>
18
+ <link
19
+ rel="stylesheet"
20
+ href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
21
+ />
22
+ <link rel="icon" href="{{ url_for('static', path='favicon.svg') }}" />
23
+ </head>
24
+ <body data-bs-theme="dark">
25
+ <div class="container py-5 h-100">
26
+ <div
27
+ class="row d-flex justify-content-center align-items-center h-100"
28
+ >
29
+ <div class="col-12 col-md-8 col-lg-6 col-xl-5">
30
+ <div class="mb-md-5 mt-md-4 pb-5">
31
+ <h2 class="fw-bold mb-3 text-uppercase">Login</h2>
32
+ <form method="post">
33
+ <div class="form-outline form-white mb-4">
34
+ <input
35
+ type="email"
36
+ id="username"
37
+ name="username"
38
+ placeholder="Email"
39
+ class="form-control form-control-lg"
40
+ />
41
+ </div>
22
42
 
23
- <div class="form-outline form-white mb-4">
24
- <input type="password" id="password" name="password" placeholder="Password" class="form-control form-control-lg" />
25
- </div>
43
+ <div class="form-outline form-white mb-4">
44
+ <input
45
+ type="password"
46
+ id="password"
47
+ name="password"
48
+ placeholder="Password"
49
+ class="form-control form-control-lg"
50
+ />
51
+ </div>
26
52
 
27
- <button class="btn btn-outline-light btn-lg px-5 w-100" type="submit">Login</button>
28
- </form>
53
+ <button
54
+ class="btn btn-outline-light btn-lg px-5 w-100"
55
+ type="submit"
56
+ >
57
+ Login
58
+ </button>
59
+ </form>
60
+ </div>
29
61
  </div>
30
62
  </div>
31
63
  </div>
32
- </div>
33
- </body>
64
+ </body>
34
65
  </html>
@@ -1,5 +1,4 @@
1
- {% extends "nav.html" %}
2
- {% block content %}
1
+ {% extends "nav.html" %} {% block content %}
3
2
  <div class="container-fluid">
4
3
  <div class="row p-2 d-flex justify-content-center">
5
4
  <div class="col col-12 col-lg-9">
@@ -8,8 +7,19 @@
8
7
  <h3>Logs - {{ device }}</h3>
9
8
  </div>
10
9
  <div class="card-header">
11
- <div class="progress m-2" role="progressbar" aria-label="Basic example" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
12
- <div class="progress-bar progress-bar-striped progress-bar-animated" id="install-progress" style="width: 0%"></div>
10
+ <div
11
+ class="progress m-2"
12
+ role="progressbar"
13
+ aria-label="Basic example"
14
+ aria-valuenow="0"
15
+ aria-valuemin="0"
16
+ aria-valuemax="100"
17
+ >
18
+ <div
19
+ class="progress-bar progress-bar-striped progress-bar-animated"
20
+ id="install-progress"
21
+ style="width: 0%"
22
+ ></div>
13
23
  </div>
14
24
  </div>
15
25
  <div class="card-body">
@@ -20,7 +30,7 @@
20
30
  </div>
21
31
  </div>
22
32
  <script>
23
- device = "{{ device }}"
33
+ device = "{{ device }}";
24
34
  </script>
25
35
  <script src="{{ url_for('static', path='js/logs.js') }}"></script>
26
36
  {% endblock content %}
@@ -1,66 +1,117 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <script>
5
5
  const PERMISSIONS = {{request.user.get_json_permissions() | safe}};
6
6
  </script>
7
- <meta charset="utf-8">
8
- <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <meta charset="utf-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
9
9
  <title>{{title}}</title>
10
10
  <!--bootstrap-->
11
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
12
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
13
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
11
+ <link
12
+ href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
13
+ rel="stylesheet"
14
+ integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
15
+ crossorigin="anonymous"
16
+ />
17
+ <script
18
+ src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
19
+ integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
20
+ crossorigin="anonymous"
21
+ ></script>
22
+ <link
23
+ rel="stylesheet"
24
+ href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
25
+ />
14
26
  <!--data tables-->
15
- <link href="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.0.1/b-3.0.0/r-3.0.0/sl-2.0.0/datatables.min.css" rel="stylesheet">
27
+ <link
28
+ href="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.0.1/b-3.0.0/r-3.0.0/sl-2.0.0/datatables.min.css"
29
+ rel="stylesheet"
30
+ />
16
31
  <script src="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.0.1/b-3.0.0/r-3.0.0/sl-2.0.0/datatables.min.js"></script>
17
32
  <!--favicon-->
18
- <link rel="icon" href="{{ url_for('static', path='favicon.svg') }}">
33
+ <link rel="icon" href="{{ url_for('static', path='favicon.svg') }}" />
19
34
  <!--data tables alignment fix-->
20
35
  <style>
21
36
  th.dt-type-numeric {
22
- text-align: left!important;
37
+ text-align: left !important;
23
38
  }
24
39
  td.dt-type-numeric {
25
- text-align: left!important;
40
+ text-align: left !important;
41
+ }
42
+ .active {
43
+ color: var(--bs-nav-pills-link-active-color);
44
+ background-color: transparent;
26
45
  }
27
46
  </style>
28
47
  <script>
29
48
  const TABLE_UPDATE_TIME = 3000;
30
49
  </script>
50
+ <script src="{{ url_for('static', path='js/util.js') }}"></script>
31
51
  </head>
32
52
  <body data-bs-theme="dark">
33
53
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
34
54
  <div class="container-fluid">
35
55
  <a class="navbar-brand" href="/ui/home">
36
- <img src="{{ request.url_for('static', path='svg/goosebit-logo.svg') }}" class="me-2" style="height:30px; width: 30px;"/>
56
+ <img
57
+ src="{{ request.url_for('static', path='svg/goosebit-logo.svg') }}"
58
+ class="me-2"
59
+ style="height: 30px; width: 30px"
60
+ />
37
61
  gooseBit
38
62
  </a>
39
- <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
63
+ <button
64
+ class="navbar-toggler"
65
+ type="button"
66
+ data-bs-toggle="collapse"
67
+ data-bs-target="#navbar"
68
+ aria-controls="navbar"
69
+ aria-expanded="false"
70
+ aria-label="Toggle navigation"
71
+ >
40
72
  <span class="navbar-toggler-icon"></span>
41
73
  </button>
42
74
  <div class="collapse navbar-collapse" id="navbar">
43
75
  <div class="navbar-nav">
44
76
  {% if "home.read" in request.user.permissions %}
45
- <a class="nav-link{% if request.url.path.endswith('home') %} active{% endif %}" href="/ui/home">Home</a>
46
- {% endif %}
47
- {% if "firmware.read" in request.user.permissions %}
48
- <a class="nav-link{% if request.url.path.endswith('firmware') %} active{% endif %}" href="/ui/firmware">Firmware</a>
49
- {% endif %}
50
- {% if "devices.read" in request.user.permissions %}
51
- <a class="nav-link{% if request.url.path.endswith('devices') %} active{% endif %}" href="/ui/devices">Devices</a>
52
- {% endif %}
53
- {% if "rollouts.read" in request.user.permissions %}
54
- <a class="nav-link{% if request.url.path.endswith('rollouts') %} active{% endif %}" href="/ui/rollouts">Rollouts</a>
77
+ <a
78
+ class="nav-link{% if request.url.path.endswith('home') %} active{% endif %}"
79
+ href="/ui/home"
80
+ >Home</a
81
+ >
82
+ {% endif %} {% if "firmware.read" in
83
+ request.user.permissions %}
84
+ <a
85
+ class="nav-link{% if request.url.path.endswith('firmware') %} active{% endif %}"
86
+ href="/ui/firmware"
87
+ >Firmware</a
88
+ >
89
+ {% endif %} {% if "device.read" in
90
+ request.user.permissions %}
91
+ <a
92
+ class="nav-link{% if request.url.path.endswith('devices') %} active{% endif %}"
93
+ href="/ui/devices"
94
+ >Devices</a
95
+ >
96
+ {% endif %} {% if "rollout.read" in
97
+ request.user.permissions %}
98
+ <a
99
+ class="nav-link{% if request.url.path.endswith('rollouts') %} active{% endif %}"
100
+ href="/ui/rollouts"
101
+ >Rollouts</a
102
+ >
55
103
  {% endif %}
56
104
  </div>
57
- <div class="navbar-nav d-flex flex-fill justify-content-end">
58
- <a class="nav-link" href="/logout">Logout<i class="bi bi-box-arrow-right ps-2"></i></a>
105
+ <div
106
+ class="navbar-nav d-flex flex-fill justify-content-end"
107
+ >
108
+ <a class="nav-link" href="/logout"
109
+ >Logout<i class="bi bi-box-arrow-right ps-2"></i
110
+ ></a>
59
111
  </div>
60
112
  </div>
61
113
  </div>
62
114
  </nav>
63
- {% block content %}
64
- {% endblock content %}
115
+ {% block content %} {% endblock content %}
65
116
  </body>
66
117
  </html>
@@ -1,53 +1,76 @@
1
- {% extends "nav.html" %}
2
- {% block content %}
1
+ {% extends "nav.html" %} {% block content %}
3
2
  <div class="container-fluid">
4
3
  <div class="row p-2 d-flex justify-content-center">
5
4
  <div class="col">
6
5
  <table id="rollout-table" class="table table-hover">
7
6
  <thead>
8
7
  <tr>
9
- <th>
10
- Id
11
- </th>
12
- <th>
13
- Created
14
- </th>
15
- <th>
16
- Name
17
- </th>
18
- <th>
19
- Model
20
- </th>
21
- <th>
22
- Revision
23
- </th>
24
- <th>
25
- Feed
26
- </th>
27
- <th>
28
- Flavour
29
- </th>
30
- <th>
31
- Update File
32
- </th>
33
- <th>
34
- Paused
35
- </th>
36
- <th>
37
- Success Count
38
- </th>
39
- <th>
40
- Failure Count
41
- </th>
8
+ <th>Id</th>
9
+ <th>Created</th>
10
+ <th>Name</th>
11
+ <th>Feed</th>
12
+ <th>Flavour</th>
13
+ <th>Firmware File</th>
14
+ <th>Firmware Version</th>
15
+ <th>Paused</th>
16
+ <th>Success Count</th>
17
+ <th>Failure Count</th>
42
18
  </tr>
43
19
  </thead>
44
- <tbody id="rollouts-list">
45
-
46
- </tbody>
20
+ <tbody id="rollouts-list"></tbody>
47
21
  </table>
48
22
  </div>
49
23
  </div>
50
24
  </div>
51
-
25
+ <div class="modal" id="rollout-create-modal">
26
+ <div class="modal-dialog">
27
+ <div class="modal-content">
28
+ <div class="modal-header">
29
+ <h5 class="modal-title">Create Rollout</h5>
30
+ <button
31
+ type="button"
32
+ class="btn-close"
33
+ data-bs-dismiss="modal"
34
+ aria-label="Close"
35
+ ></button>
36
+ </div>
37
+ <div class="modal-body">
38
+ <input
39
+ id="rollout-selected-name"
40
+ class="form-control mb-3"
41
+ placeholder="Name"
42
+ />
43
+ <input
44
+ id="rollout-selected-feed"
45
+ class="form-control mb-3"
46
+ placeholder="Feed"
47
+ />
48
+ <input
49
+ id="rollout-selected-flavor"
50
+ class="form-control mb-3"
51
+ placeholder="Flavor"
52
+ />
53
+ <select class="form-select" id="selected-fw"></select>
54
+ </div>
55
+ <div class="modal-footer">
56
+ <button
57
+ type="button"
58
+ class="btn btn-secondary"
59
+ data-bs-dismiss="modal"
60
+ >
61
+ Close
62
+ </button>
63
+ <button
64
+ type="button"
65
+ class="btn btn-outline-light"
66
+ data-bs-dismiss="modal"
67
+ onclick="createRollout()"
68
+ >
69
+ Save changes
70
+ </button>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
52
75
  <script src="{{ url_for('static', path='js/rollouts.js') }}"></script>
53
76
  {% endblock content %}
@@ -1 +1 @@
1
- from .routes import router
1
+ from .routes import router # noqa: F401
@@ -1 +1 @@
1
- from .routes import router
1
+ from .routes import router # noqa: F401
@@ -1 +1 @@
1
- from .routes import router
1
+ from .routes import router # noqa: F401
@@ -1,12 +1,16 @@
1
1
  import json
2
+ import logging
2
3
 
3
4
  from fastapi import APIRouter, Depends
4
5
  from fastapi.requests import Request
5
6
 
7
+ from goosebit.models import Firmware, UpdateStateEnum
6
8
  from goosebit.settings import POLL_TIME_REGISTRATION
7
- from goosebit.updater.manager import UpdateManager, get_update_manager
9
+ from goosebit.updater.manager import HandlingType, UpdateManager, get_update_manager
10
+ from goosebit.updates import generate_chunk
11
+
12
+ logger = logging.getLogger("DDI API")
8
13
 
9
- # v1 is hardware revision
10
14
  router = APIRouter(prefix="/v1")
11
15
 
12
16
 
@@ -20,9 +24,9 @@ async def polling(
20
24
  links = {}
21
25
 
22
26
  sleep = updater.poll_time
23
- last_state = updater.device.last_state
27
+ device = await updater.get_device()
24
28
 
25
- if last_state == "unknown":
29
+ if device.last_state == UpdateStateEnum.UNKNOWN:
26
30
  # device registration
27
31
  sleep = POLL_TIME_REGISTRATION
28
32
  links["configData"] = {
@@ -34,26 +38,28 @@ async def polling(
34
38
  )
35
39
  )
36
40
  }
41
+ logger.info(f"Skip: registration required, device={updater.dev_id}")
37
42
 
38
- elif last_state == "error" and not updater.force_update:
39
- # nothing to do
43
+ elif device.last_state == UpdateStateEnum.ERROR and not device.force_update:
44
+ logger.warning(f"Skip: device in error state, device={updater.dev_id}")
40
45
  pass
41
46
 
42
47
  else:
43
48
  # provide update if available. Note: this is also required while in state "running", otherwise swupdate
44
49
  # won't confirm a successful testing (might be a bug/problem in swupdate)
45
- update = await updater.get_update_mode()
46
- if update != "skip":
50
+ handling_type, firmware = await updater.get_update()
51
+ if handling_type != HandlingType.SKIP:
47
52
  links["deploymentBase"] = {
48
53
  "href": str(
49
54
  request.url_for(
50
55
  "deployment_base",
51
56
  tenant=tenant,
52
57
  dev_id=dev_id,
53
- action_id=1,
58
+ action_id=firmware.id,
54
59
  )
55
60
  )
56
61
  }
62
+ logger.info(f"Forced: update available, device={updater.dev_id}")
57
63
 
58
64
  return {
59
65
  "config": {"polling": {"sleep": sleep}},
@@ -71,6 +77,7 @@ async def config_data(
71
77
  data = await request.json()
72
78
  # TODO: make standard schema to deal with this
73
79
  await updater.update_config_data(**data["data"])
80
+ logger.info(f"Updating config data, device={updater.dev_id}")
74
81
  return {"success": True, "message": "Updated swupdate data."}
75
82
 
76
83
 
@@ -82,16 +89,16 @@ async def deployment_base(
82
89
  action_id: int,
83
90
  updater: UpdateManager = Depends(get_update_manager),
84
91
  ):
85
- artifact = await updater.get_update_file()
86
- update = await updater.get_update_mode()
87
- await updater.save()
92
+ handling_type, firmware = await updater.get_update()
93
+
94
+ logger.info(f"Request deployment base, device={updater.dev_id}")
88
95
 
89
96
  return {
90
97
  "id": f"{action_id}",
91
98
  "deployment": {
92
- "download": update,
93
- "update": update,
94
- "chunks": artifact.generate_chunk(request, tenant=tenant, dev_id=dev_id),
99
+ "download": str(handling_type),
100
+ "update": str(handling_type),
101
+ "chunks": generate_chunk(request, firmware),
95
102
  },
96
103
  }
97
104
 
@@ -100,63 +107,74 @@ async def deployment_base(
100
107
  async def deployment_feedback(
101
108
  request: Request,
102
109
  tenant: str,
103
- dev_id: str,
104
110
  action_id: int,
105
111
  updater: UpdateManager = Depends(get_update_manager),
106
112
  ):
107
113
  try:
108
114
  data = await request.json()
109
- except json.JSONDecodeError:
115
+ except json.JSONDecodeError as e:
116
+ logging.warning(f"Parsing deployment feedback failed, error={e}, device={updater.dev_id}")
110
117
  return
111
118
  try:
112
119
  execution = data["status"]["execution"]
113
120
 
114
121
  if execution == "proceeding":
115
- await updater.update_device_state("running")
122
+ await updater.update_device_state(UpdateStateEnum.RUNNING)
123
+ logger.debug(f"Installation in progress, device={updater.dev_id}")
116
124
 
117
125
  elif execution == "closed":
118
126
  state = data["status"]["result"]["finished"]
119
127
 
120
- updater.force_update = False
121
- updater.update_complete = True
128
+ await updater.update_force_update(False)
129
+ await updater.update_log_complete(True)
130
+
131
+ reported_firmware = await Firmware.get_or_none(id=data["id"])
122
132
 
123
133
  # From hawkBit docu: DDI defines also a status NONE which will not be interpreted by the update server
124
134
  # and handled like SUCCESS.
125
135
  if state == "success" or state == "none":
126
- await updater.update_device_state("finished")
136
+ await updater.update_device_state(UpdateStateEnum.FINISHED)
127
137
 
128
138
  # not guaranteed to be the correct rollout - see next comment.
129
139
  rollout = await updater.get_rollout()
130
140
  if rollout:
131
- file = rollout.fw_file
132
- rollout.success_count += 1
133
- await rollout.save()
134
- else:
135
- device = await updater.get_device()
136
- file = device.fw_file
141
+ if rollout.firmware == reported_firmware:
142
+ rollout.success_count += 1
143
+ await rollout.save()
144
+ else:
145
+ logging.warning(
146
+ f"Updating rollout success stats failed, firmware={reported_firmware.id}, device={updater.dev_id}" # noqa: E501
147
+ )
137
148
 
138
149
  # setting the currently installed version based on the current assigned firmware / existing rollouts
139
150
  # is problematic. Better to assign custom action_id for each update (rollout id? firmware id? new id?).
140
151
  # Alternatively - but requires customization on the gateway side - use version reported by the gateway.
141
- await updater.update_fw_version(file)
152
+ await updater.update_fw_version(reported_firmware.version)
153
+ logger.debug(f"Installation successful, firmware={reported_firmware.version}, device={updater.dev_id}")
142
154
 
143
155
  elif state == "failure":
144
- await updater.update_device_state("error")
156
+ await updater.update_device_state(UpdateStateEnum.ERROR)
145
157
 
146
158
  # not guaranteed to be the correct rollout - see comment above.
147
159
  rollout = await updater.get_rollout()
148
160
  if rollout:
149
- rollout.failure_count += 1
150
- await rollout.save()
161
+ if rollout.firmware == reported_firmware:
162
+ rollout.failure_count += 1
163
+ await rollout.save()
164
+ else:
165
+ logging.warning(
166
+ f"Updating rollout failure stats failed, firmware={reported_firmware.id}, device={updater.dev_id}" # noqa: E501
167
+ )
151
168
 
152
- except KeyError:
153
- pass
169
+ logger.debug(f"Installation failed, firmware={reported_firmware.version}, device={updater.dev_id}")
170
+
171
+ except KeyError as e:
172
+ logging.warning(f"Processing deployment feedback failed, error={e}, device={updater.dev_id}")
154
173
 
155
174
  try:
156
175
  log = data["status"]["details"]
157
176
  await updater.update_log("\n".join(log))
158
177
  except KeyError:
159
- pass
178
+ logging.warning(f"No details to update update log, device={updater.dev_id}")
160
179
 
161
- await updater.save()
162
180
  return {"id": str(action_id)}