Merge ~lloydwaltersj/maas:image-deployment-data into maas:master
- Git
- lp:~lloydwaltersj/maas
- image-deployment-data
- Merge into master
Status: | Merged |
---|---|
Approved by: | Jack Lloyd-Walters |
Approved revision: | 29626a824458f9ec2de1b6b2d1959cc5e5fc5c4c |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~lloydwaltersj/maas:image-deployment-data |
Merge into: | maas:master |
Diff against target: |
283 lines (+95/-19) 8 files modified
src/maasserver/api/boot_resources.py (+1/-0) src/maasserver/api/tests/test_boot_resources.py (+3/-0) src/maasserver/models/bootresource.py (+19/-13) src/maasserver/models/node.py (+11/-1) src/maasserver/models/tests/test_node.py (+5/-2) src/maasserver/websockets/handlers/bootresource.py (+26/-3) src/maasserver/websockets/handlers/tests/test_bootresource.py (+26/-0) src/provisioningserver/events.py (+4/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Approve | ||
Adam Collard (community) | Approve | ||
Review via email: mp+434563@code.launchpad.net |
Commit message
Add deployment information to boot resources
Description of the change
Jack Lloyd-Walters (lloydwaltersj) wrote : | # |
jenkins: !test
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: FAILED
LOG: http://
COMMIT: dc80b0ccfc25d1c
Adam Collard (adam-collard) : | # |
- d5ce368... by Jack Lloyd-Walters
-
Replace with events
- 5896d87... by Jack Lloyd-Walters
-
formatting and linting
- fc6df1e... by Jack Lloyd-Walters
-
add type hinting and linting
- 88e1b84... by Jack Lloyd-Walters
-
Fix tests raising errors
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: FAILED
LOG: http://
COMMIT: fc6df1e7bd9324a
- bdef783... by Jack Lloyd-Walters
-
fix status change test
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: SUCCESS
COMMIT: 88e1b84eb5da9cd
Adam Collard (adam-collard) : | # |
Adam Collard (adam-collard) : | # |
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: SUCCESS
COMMIT: bdef783c5d5dd1e
- 6054bb3... by Jack Lloyd-Walters
-
remove layering violation
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: SUCCESS
COMMIT: 6054bb33c2141e1
- 600e0f7... by Jack Lloyd-Walters
-
remove unused column from database
- 4448eb7... by Jack Lloyd-Walters
-
fix restAPI issues
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: FAILED
LOG: http://
COMMIT: 600e0f7bf050133
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: SUCCESS
COMMIT: 4448eb7bb7ee931
- f128bd4... by Jack Lloyd-Walters
-
respond to comments
- 29626a8... by Jack Lloyd-Walters
-
remove erroneous code
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b image-deploymen
STATUS: SUCCESS
COMMIT: 29626a824458f9e
Preview Diff
1 | diff --git a/src/maasserver/api/boot_resources.py b/src/maasserver/api/boot_resources.py |
2 | index 6100c89..770fdf1 100644 |
3 | --- a/src/maasserver/api/boot_resources.py |
4 | +++ b/src/maasserver/api/boot_resources.py |
5 | @@ -108,6 +108,7 @@ def boot_resource_to_dict(resource, with_sets=False): |
6 | "name": resource.name, |
7 | "architecture": resource.architecture, |
8 | "resource_uri": reverse("boot_resource_handler", args=[resource.id]), |
9 | + "last_deployed": resource.get_last_deploy(), |
10 | } |
11 | dict_representation.update(resource.extra) |
12 | if with_sets: |
13 | diff --git a/src/maasserver/api/tests/test_boot_resources.py b/src/maasserver/api/tests/test_boot_resources.py |
14 | index a8ac72c..a4e0dc5 100644 |
15 | --- a/src/maasserver/api/tests/test_boot_resources.py |
16 | +++ b/src/maasserver/api/tests/test_boot_resources.py |
17 | @@ -101,6 +101,9 @@ class TestHelpers(APITestCase.ForUser): |
18 | get_boot_resource_uri(resource), |
19 | dict_representation["resource_uri"], |
20 | ) |
21 | + self.assertEqual( |
22 | + resource.get_last_deploy(), dict_representation["last_deployed"] |
23 | + ) |
24 | self.assertFalse("sets" in dict_representation) |
25 | |
26 | def test_boot_resource_to_dict_with_sets(self): |
27 | diff --git a/src/maasserver/models/bootresource.py b/src/maasserver/models/bootresource.py |
28 | index 3ec9528..7fbb860 100644 |
29 | --- a/src/maasserver/models/bootresource.py |
30 | +++ b/src/maasserver/models/bootresource.py |
31 | @@ -1,9 +1,9 @@ |
32 | -# Copyright 2014-2018 Canonical Ltd. This software is licensed under the |
33 | +# Copyright 2014-2022 Canonical Ltd. This software is licensed under the |
34 | # GNU Affero General Public License version 3 (see the file LICENSE). |
35 | |
36 | """Boot Resource.""" |
37 | |
38 | - |
39 | +from datetime import datetime |
40 | from operator import attrgetter |
41 | |
42 | from django.core.exceptions import ValidationError |
43 | @@ -16,7 +16,6 @@ from django.db.models import ( |
44 | Prefetch, |
45 | Sum, |
46 | ) |
47 | -from django.utils import timezone |
48 | |
49 | from maasserver.enum import ( |
50 | BOOT_RESOURCE_FILE_TYPE, |
51 | @@ -516,16 +515,6 @@ class BootResource(CleanSave, TimestampedModel): |
52 | """Return rtype text as displayed to the user.""" |
53 | return BOOT_RESOURCE_TYPE_CHOICES_DICT[self.rtype] |
54 | |
55 | - @property |
56 | - def last_deployed(self) -> timezone.datetime: |
57 | - """Returns the most recent time of deplyment for an image.""" |
58 | - # Mock data: Generates a random time based on the hash of the |
59 | - # resource name. |
60 | - ms_py = 3153600000000 |
61 | - return timezone.datetime(2022, 6, 1) + timezone.timedelta( |
62 | - microseconds=hash(self.name) % ms_py |
63 | - ) |
64 | - |
65 | def clean(self): |
66 | """Validate the model. |
67 | |
68 | @@ -591,6 +580,23 @@ class BootResource(CleanSave, TimestampedModel): |
69 | return resource_set |
70 | return None |
71 | |
72 | + def get_last_deploy(self) -> datetime: |
73 | + from maasserver.models.event import Event |
74 | + from provisioningserver.events import EVENT_TYPES |
75 | + |
76 | + deploy_msg = f"deployed {self.name}/{self.architecture}" |
77 | + try: |
78 | + return ( |
79 | + Event.objects.filter( |
80 | + type__name=EVENT_TYPES.IMAGE_DEPLOYED, |
81 | + description=deploy_msg, |
82 | + ) |
83 | + .latest("created") |
84 | + .created |
85 | + ) |
86 | + except Event.DoesNotExist: |
87 | + pass |
88 | + |
89 | def split_arch(self): |
90 | return self.architecture.split("/") |
91 | |
92 | diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py |
93 | index e1c6923..e9766d6 100644 |
94 | --- a/src/maasserver/models/node.py |
95 | +++ b/src/maasserver/models/node.py |
96 | @@ -1,4 +1,4 @@ |
97 | -# Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
98 | +# Copyright 2012-2022 Canonical Ltd. This software is licensed under the |
99 | # GNU Affero General Public License version 3 (see the file LICENSE). |
100 | |
101 | """Node objects.""" |
102 | @@ -1789,11 +1789,21 @@ class Node(CleanSave, TimestampedModel): |
103 | self.update_status(NODE_STATUS.DEPLOYED) |
104 | if self.enable_hw_sync: |
105 | self.last_sync = datetime.now() |
106 | + self.update_deployment_time() |
107 | self.save() |
108 | |
109 | # Create a status message for DEPLOYED. |
110 | Event.objects.create_node_event(self, EVENT_TYPES.DEPLOYED) |
111 | |
112 | + def update_deployment_time(self) -> None: |
113 | + from maasserver.models.event import Event |
114 | + |
115 | + Event.objects.create_node_event( |
116 | + self, |
117 | + EVENT_TYPES.IMAGE_DEPLOYED, |
118 | + event_description=f"deployed {self.osystem}/{self.distro_series}/{self.architecture}", |
119 | + ) |
120 | + |
121 | def ip_addresses(self, ifaces=None): |
122 | """IP addresses allocated to this node. |
123 | |
124 | diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py |
125 | index 3aba41b..4cf6669 100644 |
126 | --- a/src/maasserver/models/tests/test_node.py |
127 | +++ b/src/maasserver/models/tests/test_node.py |
128 | @@ -4823,9 +4823,12 @@ class TestNode(MAASServerTestCase): |
129 | self.disable_node_query() |
130 | node = factory.make_Node(status=NODE_STATUS.DEPLOYING) |
131 | node.end_deployment() |
132 | - event = Event.objects.get(node=node) |
133 | + events = Event.objects.filter(node=node) |
134 | self.assertEqual(NODE_STATUS.DEPLOYED, reload_object(node).status) |
135 | - self.assertEqual(event.type.name, EVENT_TYPES.DEPLOYED) |
136 | + self.assertEqual( |
137 | + {event.type.name for event in events}, |
138 | + {EVENT_TYPES.IMAGE_DEPLOYED, EVENT_TYPES.DEPLOYED}, |
139 | + ) |
140 | |
141 | def test_end_deployment_sets_first_last_sync_value(self): |
142 | self.disable_node_query() |
143 | diff --git a/src/maasserver/websockets/handlers/bootresource.py b/src/maasserver/websockets/handlers/bootresource.py |
144 | index 908af1e..fa4b17c 100644 |
145 | --- a/src/maasserver/websockets/handlers/bootresource.py |
146 | +++ b/src/maasserver/websockets/handlers/bootresource.py |
147 | @@ -5,6 +5,8 @@ |
148 | |
149 | |
150 | from collections import defaultdict |
151 | +from datetime import datetime |
152 | +from typing import Optional |
153 | |
154 | from distro_info import UbuntuDistroInfo |
155 | from django.core.exceptions import ValidationError |
156 | @@ -376,7 +378,9 @@ class BootResourceHandler(Handler): |
157 | count += 1 |
158 | return count |
159 | |
160 | - def pick_latest_datetime(self, time, other_time): |
161 | + def pick_latest_datetime( |
162 | + self, time: datetime, other_time: datetime |
163 | + ) -> datetime: |
164 | """Return the datetime that is the latest.""" |
165 | if time is None: |
166 | return other_time |
167 | @@ -410,7 +414,9 @@ class BootResourceHandler(Handler): |
168 | return False |
169 | return True |
170 | |
171 | - def get_last_update_for_resources(self, resources): |
172 | + def get_last_update_for_resources( |
173 | + self, resources: list[BootResource] |
174 | + ) -> datetime: |
175 | """Return the latest updated time for all resources.""" |
176 | last_update = None |
177 | for resource in resources: |
178 | @@ -431,6 +437,19 @@ class BootResourceHandler(Handler): |
179 | for resource in resources |
180 | ) |
181 | |
182 | + def get_last_deployed_for_resources( |
183 | + self, resources: list[BootResource] |
184 | + ) -> Optional[datetime]: |
185 | + """Return the most recent deploy time for all resources.""" |
186 | + last_deployed = None |
187 | + for resource in resources: |
188 | + this_deploy = resource.get_last_deploy() |
189 | + if this_deploy is not None: |
190 | + last_deployed = self.pick_latest_datetime( |
191 | + last_deployed, this_deploy |
192 | + ) |
193 | + return last_deployed |
194 | + |
195 | def get_progress_for_resources(self, resources): |
196 | """Return the overall progress for all resources.""" |
197 | size = 0 |
198 | @@ -479,6 +498,7 @@ class BootResourceHandler(Handler): |
199 | number_of_nodes = self.get_number_of_nodes_for_resources(group) |
200 | complete = self.are_all_resources_complete(group) |
201 | progress = self.get_progress_for_resources(group) |
202 | + last_deployed = self.get_last_deployed_for_resources(group) |
203 | |
204 | # Set the computed attributes on the first resource as that will |
205 | # be the only one returned to the UI. |
206 | @@ -489,6 +509,7 @@ class BootResourceHandler(Handler): |
207 | resource.size = human_readable_bytes(unique_size) |
208 | resource.last_update = last_update |
209 | resource.number_of_nodes = number_of_nodes |
210 | + resource.last_deployed = last_deployed |
211 | resource.complete = complete |
212 | if not complete: |
213 | if progress > 0: |
214 | @@ -599,7 +620,9 @@ class BootResourceHandler(Handler): |
215 | ), |
216 | "lastDeployed": resource.last_deployed.strftime( |
217 | "%a, %d %b. %Y %H:%M:%S" |
218 | - ), |
219 | + ) |
220 | + if resource.last_deployed |
221 | + else None, |
222 | } |
223 | for resource in self.combine_resources( |
224 | BootResource.objects.filter(bootloader_type=None) |
225 | diff --git a/src/maasserver/websockets/handlers/tests/test_bootresource.py b/src/maasserver/websockets/handlers/tests/test_bootresource.py |
226 | index d1f401b..96c773f 100644 |
227 | --- a/src/maasserver/websockets/handlers/tests/test_bootresource.py |
228 | +++ b/src/maasserver/websockets/handlers/tests/test_bootresource.py |
229 | @@ -364,6 +364,32 @@ class TestBootResourcePoll(MAASServerTestCase, PatchOSInfoMixin): |
230 | resource = response["resources"][0] |
231 | self.assertEqual(version, resource["title"]) |
232 | |
233 | + def test_shows_last_deployment_time(self) -> None: |
234 | + owner = factory.make_admin() |
235 | + handler = BootResourceHandler(owner, {}, None) |
236 | + resource = factory.make_usable_boot_resource( |
237 | + rtype=BOOT_RESOURCE_TYPE.SYNCED |
238 | + ) |
239 | + os_name, series = resource.name.split("/") |
240 | + # The polled datetime only has granularity of order seconds |
241 | + start_time = datetime.datetime.now().replace(microsecond=0) |
242 | + node = factory.make_Node( |
243 | + status=NODE_STATUS.DEPLOYED, |
244 | + osystem=os_name, |
245 | + distro_series=series, |
246 | + architecture=resource.architecture, |
247 | + ) |
248 | + node.end_deployment() |
249 | + response = handler.poll({}) |
250 | + resource = response["resources"][0] |
251 | + self.assertIn("lastDeployed", resource) |
252 | + self.assertGreaterEqual( |
253 | + datetime.datetime.strptime( |
254 | + resource["lastDeployed"], "%a, %d %b. %Y %H:%M:%S" |
255 | + ), |
256 | + start_time, |
257 | + ) |
258 | + |
259 | def test_shows_number_of_nodes_deployed_for_resource(self): |
260 | owner = factory.make_admin() |
261 | handler = BootResourceHandler(owner, {}, None) |
262 | diff --git a/src/provisioningserver/events.py b/src/provisioningserver/events.py |
263 | index 6d6cceb..bffd6ae 100644 |
264 | --- a/src/provisioningserver/events.py |
265 | +++ b/src/provisioningserver/events.py |
266 | @@ -147,6 +147,7 @@ class EVENT_TYPES: |
267 | READY = "READY" |
268 | DEPLOYING = "DEPLOYING" |
269 | DEPLOYED = "DEPLOYED" |
270 | + IMAGE_DEPLOYED = "IMAGE_DEPLOYED" |
271 | RELEASING = "RELEASING" |
272 | RELEASED = "RELEASED" |
273 | ENTERING_RESCUE_MODE = "ENTERING_RESCUE_MODE" |
274 | @@ -411,6 +412,9 @@ EVENT_DETAILS = { |
275 | EVENT_TYPES.READY: EventDetail(description="Ready", level=INFO), |
276 | EVENT_TYPES.DEPLOYING: EventDetail(description="Deploying", level=INFO), |
277 | EVENT_TYPES.DEPLOYED: EventDetail(description="Deployed", level=INFO), |
278 | + EVENT_TYPES.IMAGE_DEPLOYED: EventDetail( |
279 | + description="Image Deployed", level=INFO |
280 | + ), |
281 | EVENT_TYPES.RELEASING: EventDetail(description="Releasing", level=INFO), |
282 | EVENT_TYPES.RELEASED: EventDetail(description="Released", level=INFO), |
283 | EVENT_TYPES.ENTERING_RESCUE_MODE: EventDetail( |
UNIT TESTS t-data lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
-b image-deploymen
STATUS: FAILED maas-ci. internal: 8080/job/ maas-tester/ 1616/consoleTex t 67530edf6d73d3e bbb7c46bd8
LOG: http://
COMMIT: 3a8d73cfb8628de