Skip to content

Commit 09f9102

Browse files
authored
Merge branch 'gsoc25' into issue/show-mass-upgrade-progress
2 parents d3101a0 + 9c3231c commit 09f9102

File tree

19 files changed

+929
-10
lines changed

19 files changed

+929
-10
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ within the OpenWISP architecture.
3838

3939
./user/intro.rst
4040
./user/quickstart.rst
41+
./user/upgrade-status.rst
4142
./user/automatic-device-firmware-detection.rst
4243
./user/custom-firmware-upgrader.rst
4344
./user/rest-api.rst

docs/user/rest-api.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,18 @@ Get Upgrade Operation Details
299299
300300
GET /api/v1/firmware-upgrader/upgrade-operation/{id}
301301
302+
Cancel Upgrade Operation
303+
~~~~~~~~~~~~~~~~~~~~~~~~
304+
305+
.. code-block:: text
306+
307+
POST /api/v1/firmware-upgrader/upgrade-operation/{id}/cancel/
308+
309+
.. note::
310+
311+
This endpoint may return a 409 status code if the operation cannot be
312+
canceled.
313+
302314
List Device Upgrade Operations
303315
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
304316

docs/user/upgrade-status.rst

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
Upgrade Status Reference
2+
========================
3+
4+
.. contents:: **Table of contents**:
5+
:depth: 2
6+
:local:
7+
8+
Overview
9+
--------
10+
11+
OpenWISP Firmware Upgrader tracks the progress of firmware upgrade
12+
operations through different status values. Understanding these statuses
13+
is essential for monitoring upgrade operations and troubleshooting issues.
14+
15+
In Progress
16+
~~~~~~~~~~~
17+
18+
**Status**: ``in-progress``
19+
20+
**Description**: The upgrade operation is currently running. This includes
21+
all phases of the upgrade process: device connection, firmware validation,
22+
file upload, and firmware flashing.
23+
24+
**What happens during this status:**
25+
26+
- Device identity verification
27+
- Firmware image validation
28+
- Some non-critical services may be stopped to free up memory, if needed
29+
- Image upload to the device
30+
- Firmware flashing process
31+
32+
**User Actions**: Users can cancel upgrade operations that are in
33+
progress, but only before the firmware flashing phase begins (typically
34+
when progress is below 60%).
35+
36+
Success
37+
~~~~~~~
38+
39+
**Status**: ``success``
40+
41+
**Description**: The firmware upgrade completed successfully. The device
42+
has been upgraded to the new firmware version and is functioning properly.
43+
44+
**What this means:**
45+
46+
- The firmware was successfully flashed to the device
47+
- The device rebooted with the new firmware
48+
- Connectivity was restored after the upgrade
49+
- All verification checks passed
50+
51+
**Next Steps**: No action required. The upgrade is complete and the device
52+
is running the new firmware.
53+
54+
Failed
55+
~~~~~~
56+
57+
**Status**: ``failed``
58+
59+
**Description**: The upgrade operation completed, but the system could not
60+
reach the device again after the upgrade.
61+
62+
**Common causes:**
63+
64+
- Hardware failures
65+
- Unexpected system errors
66+
- The network became unreachable after flashing the new firmware
67+
68+
**Recommended Actions**:
69+
70+
- Check network connectivity
71+
- Physical inspection and/or serial console debugging
72+
73+
Aborted
74+
~~~~~~~
75+
76+
**Status**: ``aborted``
77+
78+
**Description**: The upgrade operation was stopped due to pre-requisites
79+
not being met. The system determined it was unsafe or impossible to
80+
proceed with the upgrade.
81+
82+
**Common causes:**
83+
84+
- Device UUID mismatch (wrong device targeted)
85+
- Insufficient memory on the device
86+
- Invalid or corrupted firmware image
87+
88+
**What happens when aborted:**
89+
90+
- The upgrade stops immediately
91+
- If services were stopped to free up memory, they are automatically
92+
restarted
93+
- No firmware changes are made to the device
94+
- Device remains in its original state
95+
96+
**Recommended Actions**:
97+
98+
- Verify the correct device is selected
99+
- Check firmware image compatibility
100+
- Ensure device has sufficient memory
101+
102+
Cancelled
103+
~~~~~~~~~
104+
105+
**Status**: ``cancelled``
106+
107+
**Description**: The upgrade operation was manually stopped by the user
108+
before completion. This is a deliberate action taken through the admin
109+
interface or REST API.
110+
111+
Users can cancel upgrades through the admin interface using the "Cancel"
112+
button that appears next to in-progress operations.
113+
114+
**When cancellation is possible:**
115+
116+
- During the early stages of upgrade (typically before 60% progress)
117+
- Before the new firmware image is written to the flash memory of the
118+
network device
119+
- While the operation status is still "in-progress"
120+
121+
**What happens when the upgrade operation cancels:**
122+
123+
- The upgrade process stops immediately
124+
- If services were stopped during the upgrade, they are automatically
125+
restarted
126+
- No firmware changes are made to the device
127+
- Device remains in its original state
128+
129+
Status Flow
130+
-----------
131+
132+
The typical flow of upgrade statuses follows this pattern:
133+
134+
.. code-block:: none
135+
136+
in-progress → success
137+
138+
failed/aborted/cancelled
139+
140+
**Typical successful upgrade:**
141+
142+
1. ``in-progress``
143+
2. ``success``
144+
145+
**Typical problematic upgrade:**
146+
147+
1. ``in-progress`` 3. ``failed``: an unexpected error occurs during
148+
upgrade 2. **OR** ``aborted``: the system detects pre-condition failure
149+
and stops safely 4. **OR** ``cancelled``: the user manually stops the
150+
upgrade
151+
152+
Monitoring Upgrades
153+
-------------------
154+
155+
**Real-time Progress**: The admin interface provides real-time updates of
156+
upgrade operations, including progress percentages and detailed logs.
157+
158+
**Upgrade Logs**: Each status change is logged with detailed information
159+
about what occurred during the upgrade process.
160+
161+
**Batch Operations**: When performing mass upgrades, you can monitor the
162+
status of individual device upgrades within the batch operation.

openwisp_firmware_upgrader/admin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ class BatchUpgradeOperationAdmin(ReadonlyUpgradeOptionsMixin, ReadOnlyAdmin, Bas
312312
"success_rate",
313313
"failed_rate",
314314
"aborted_rate",
315+
"cancelled_rate",
315316
"readonly_upgrade_options",
316317
"created",
317318
"modified",
@@ -322,6 +323,7 @@ class BatchUpgradeOperationAdmin(ReadonlyUpgradeOptionsMixin, ReadOnlyAdmin, Bas
322323
"success_rate",
323324
"failed_rate",
324325
"aborted_rate",
326+
"cancelled_rate",
325327
"readonly_upgrade_options",
326328
]
327329
change_form_template = (
@@ -471,6 +473,9 @@ def failed_rate(self, obj):
471473
def aborted_rate(self, obj):
472474
return self.__get_rate(obj.aborted_rate)
473475

476+
def cancelled_rate(self, obj):
477+
return self.__get_rate(obj.cancelled_rate)
478+
474479
def __get_rate(self, value):
475480
if value:
476481
return f"{value}%"
@@ -480,6 +485,7 @@ def __get_rate(self, value):
480485
success_rate.short_description = _("success rate")
481486
failed_rate.short_description = _("failure rate")
482487
aborted_rate.short_description = _("abortion rate")
488+
cancelled_rate.short_description = _("cancellation rate")
483489

484490

485491
class DeviceFirmwareForm(forms.ModelForm):

openwisp_firmware_upgrader/api/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class BatchUpgradeOperationSerializer(BatchUpgradeOperationListSerializer):
115115
success_rate = serializers.IntegerField(read_only=True)
116116
failed_rate = serializers.IntegerField(read_only=True)
117117
aborted_rate = serializers.IntegerField(read_only=True)
118+
cancelled_rate = serializers.IntegerField(read_only=True)
118119
upgradeoperations = UpgradeOperationSerializer(
119120
read_only=True, source="upgradeoperation_set", many=True
120121
)

openwisp_firmware_upgrader/api/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
views.upgrade_operation_detail,
5858
name="api_upgradeoperation_detail",
5959
),
60+
path(
61+
"upgrade-operation/<uuid:pk>/cancel/",
62+
views.upgrade_operation_cancel,
63+
name="api_upgradeoperation_cancel",
64+
),
6065
path(
6166
"device/<uuid:pk>/upgrade-operation/",
6267
views.device_upgrade_operation_list,

openwisp_firmware_upgrader/api/views.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import logging
2+
13
import swapper
24
from django.core.exceptions import ValidationError
35
from django.http import Http404
6+
from django.utils.translation import gettext_lazy as _
47
from django_filters.rest_framework import DjangoFilterBackend
8+
from drf_yasg import openapi
9+
from drf_yasg.utils import swagger_auto_schema
510
from rest_framework import filters, generics, pagination, serializers, status
611
from rest_framework.exceptions import NotFound, PermissionDenied
712
from rest_framework.request import clone_request
@@ -26,6 +31,8 @@
2631
UpgradeOperationSerializer,
2732
)
2833

34+
logger = logging.getLogger(__name__)
35+
2936
BatchUpgradeOperation = load_model("BatchUpgradeOperation")
3037
UpgradeOperation = load_model("UpgradeOperation")
3138
Build = load_model("Build")
@@ -344,6 +351,75 @@ def get_object_or_none(self):
344351
raise
345352

346353

354+
class UpgradeOperationCancelView(ProtectedAPIMixin, generics.GenericAPIView):
355+
queryset = UpgradeOperation.objects.all()
356+
serializer_class = serializers.Serializer
357+
lookup_field = "pk"
358+
organization_field = "device__organization"
359+
360+
@swagger_auto_schema(
361+
operation_description=_("Cancel an upgrade operation"),
362+
operation_summary=_("Cancel upgrade operation"),
363+
responses={
364+
200: openapi.Response(
365+
description=_("Upgrade operation cancelled successfully"),
366+
schema=openapi.Schema(
367+
type=openapi.TYPE_OBJECT,
368+
properties={
369+
"message": openapi.Schema(
370+
type=openapi.TYPE_STRING, description=_("Success message")
371+
)
372+
},
373+
),
374+
),
375+
409: openapi.Response(
376+
description=_("Operation cannot be cancelled"),
377+
schema=openapi.Schema(
378+
type=openapi.TYPE_OBJECT,
379+
properties={
380+
"error": openapi.Schema(
381+
type=openapi.TYPE_STRING,
382+
description=_(
383+
"Error message explaining why cancellation is not allowed"
384+
),
385+
)
386+
},
387+
),
388+
),
389+
},
390+
)
391+
def post(self, request, pk):
392+
"""Cancel an upgrade operation if conditions are met."""
393+
try:
394+
operation = self.get_object()
395+
except Http404:
396+
return self._error_response(
397+
"Upgrade operation not found", status.HTTP_404_NOT_FOUND
398+
)
399+
try:
400+
operation.cancel()
401+
except ValueError as e:
402+
return self._error_response(str(e), status.HTTP_409_CONFLICT)
403+
except Exception as e:
404+
logger.error(f"Failed to cancel upgrade operation {pk}: {str(e)}")
405+
return self._error_response(
406+
"Failed to cancel upgrade operation",
407+
status.HTTP_500_INTERNAL_SERVER_ERROR,
408+
)
409+
else:
410+
logger.info(
411+
f"Upgrade operation {pk} cancelled successfully by user {request.user}"
412+
)
413+
return Response(
414+
{"message": "Upgrade operation cancelled successfully"},
415+
status=status.HTTP_200_OK,
416+
)
417+
418+
def _error_response(self, message, status_code):
419+
"""Helper method to create consistent error responses."""
420+
return Response({"error": message}, status=status_code)
421+
422+
347423
build_list = BuildListView.as_view()
348424
build_detail = BuildDetailView.as_view()
349425
api_batch_upgrade = BuildBatchUpgradeView.as_view()
@@ -358,3 +434,4 @@ def get_object_or_none(self):
358434
upgrade_operation_detail = UpgradeOperationDetailView.as_view()
359435
device_upgrade_operation_list = DeviceUpgradeOperationListView.as_view()
360436
device_firmware_detail = DeviceFirmwareDetailView.as_view()
437+
upgrade_operation_cancel = UpgradeOperationCancelView.as_view()

0 commit comments

Comments
 (0)