Merge ~newell-jensen/maas:lp1749017 into maas:master

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: ec95cb79eb28bb8998f3de69e56b0c0988963d83
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~newell-jensen/maas:lp1749017
Merge into: maas:master
Diff against target: 149 lines (+69/-13)
2 files modified
src/maasserver/api/events.py (+16/-8)
src/maasserver/api/tests/test_events.py (+53/-5)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
Blake Rouse (community) Approve
Review via email: mp+337603@code.launchpad.net

Commit message

LP: #1749017 -- Fixes event query error for null node or user.

Description of the change

This branch fixes the bug and also adds username to the information returned by the events query API endpoint as this information is important in audit events to track the user's the event is associated with.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Change looks good. I have a suggestion that should improve performance for this API.

review: Needs Fixing
~newell-jensen/maas:lp1749017 updated
ec95cb7... by Newell Jensen

Use select_related instead of prefetch_related to reduce query count.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Awesome!

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b lp1749017 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: faca47962fefa17ab39c51e75c522af9d19ff61d

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/events.py b/src/maasserver/api/events.py
2index 72ff4f1..eee116d 100644
3--- a/src/maasserver/api/events.py
4+++ b/src/maasserver/api/events.py
5@@ -1,4 +1,4 @@
6-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
7+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 __all__ = [
11@@ -37,8 +37,15 @@ DEFAULT_EVENT_LOG_LIMIT = 100
12 def event_to_dict(event):
13 """Convert `Event` to a dictionary."""
14 return dict(
15- node=event.node.system_id,
16- hostname=event.node.hostname,
17+ username=(
18+ event.user.username
19+ if event.user is not None else event.username),
20+ node=(
21+ event.node.system_id
22+ if event.node is not None else None),
23+ hostname=(
24+ event.node.hostname
25+ if event.node is not None else event.node_hostname),
26 id=event.id,
27 level=event.type.level_str,
28 created=event.created.strftime('%a, %d %b. %Y %H:%M:%S'),
29@@ -133,20 +140,21 @@ class EventsHandler(OperationsHandler):
30 # Check first for AUDIT level.
31 if level == LOGGING_LEVELS[AUDIT]:
32 events = Event.objects.filter(type__level=AUDIT)
33- events = (events.all().prefetch_related('type'))
34 elif level in LOGGING_LEVELS_BY_NAME:
35 events = Event.objects.filter(node__in=nodes)
36 # Eliminate logs below the requested level.
37 events = events.exclude(
38 type__level__lt=LOGGING_LEVELS_BY_NAME[level])
39- events = (
40- events.all()
41- .prefetch_related('type')
42- .prefetch_related('node'))
43 elif level is not None:
44 raise MAASAPIBadRequest(
45 "Unrecognised log level: %s" % level)
46
47+ events = (
48+ events.all()
49+ .select_related('type')
50+ .select_related('node')
51+ .select_related('user'))
52+
53 # Filter events for owner.
54 if owner is not None:
55 events = events.filter(user__username=owner)
56diff --git a/src/maasserver/api/tests/test_events.py b/src/maasserver/api/tests/test_events.py
57index 2cbbf12..24ecff1 100644
58--- a/src/maasserver/api/tests/test_events.py
59+++ b/src/maasserver/api/tests/test_events.py
60@@ -1,4 +1,4 @@
61-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
62+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
63 # GNU Affero General Public License version 3 (see the file LICENSE).
64
65 """Tests for the events API."""
66@@ -20,6 +20,7 @@ from urllib.parse import (
67
68 from django.conf import settings
69 from maasserver.api import events as events_module
70+from maasserver.api.events import event_to_dict
71 from maasserver.api.tests.test_nodes import RequestFixture
72 from maasserver.enum import NODE_TYPE
73 from maasserver.testing.api import APITestCase
74@@ -36,6 +37,7 @@ from testtools.matchers import (
75 Equals,
76 HasLength,
77 Is,
78+ MatchesDict,
79 MatchesStructure,
80 Not,
81 )
82@@ -71,6 +73,54 @@ def AfterBeingDecoded(matcher):
83 matcher)
84
85
86+class TestEventToDict(APITestCase.ForUser):
87+ """Test for `event_to_dict` function."""
88+
89+ def test__node_not_None(self):
90+ event = factory.make_Event()
91+ self.assertThat(event_to_dict(event), MatchesDict({
92+ "username": Equals(event.user.username),
93+ "node": Equals(event.node.system_id),
94+ "hostname": Equals(event.node.hostname),
95+ "id": Equals(event.id),
96+ "level": Equals(event.type.level_str),
97+ "created": Equals(
98+ event.created.strftime('%a, %d %b. %Y %H:%M:%S')),
99+ "type": Equals(event.type.description),
100+ "description": Equals(event.description),
101+ }))
102+
103+ def test__node_and_user_is_None(self):
104+ event = factory.make_Event()
105+ event.node = None
106+ event.user = None
107+ self.assertThat(event_to_dict(event), MatchesDict({
108+ "username": Equals(event.username),
109+ "node": Equals(None),
110+ "hostname": Equals(event.node_hostname),
111+ "id": Equals(event.id),
112+ "level": Equals(event.type.level_str),
113+ "created": Equals(
114+ event.created.strftime('%a, %d %b. %Y %H:%M:%S')),
115+ "type": Equals(event.type.description),
116+ "description": Equals(event.description),
117+ }))
118+
119+ def test__type_level_AUDIT(self):
120+ event = factory.make_Event()
121+ self.assertThat(event_to_dict(event), MatchesDict({
122+ "username": Equals(event.user.username),
123+ "node": Equals(event.node.system_id),
124+ "hostname": Equals(event.node.hostname),
125+ "id": Equals(event.id),
126+ "level": Equals(event.type.level_str),
127+ "created": Equals(
128+ event.created.strftime('%a, %d %b. %Y %H:%M:%S')),
129+ "type": Equals(event.type.description),
130+ "description": Equals(event.render_audit_description),
131+ }))
132+
133+
134 class TestEventsAPI(APITestCase.ForUser):
135 """Tests for /api/2.0/events/."""
136
137@@ -588,10 +638,8 @@ class TestEventsAPI(APITestCase.ForUser):
138 make_events(number_events, node=node)
139
140 def test_query_num_queries_is_independent_of_num_nodes_and_events(self):
141- # 1 query for select event +
142- # 1 query to prefetch eventtype +
143- # 1 query to prefetch node details
144- expected_queries = 3
145+ # 1 query for all the select_related's.
146+ expected_queries = 1
147 events_per_node = 5
148 num_nodes_per_group = 5
149 events_per_group = num_nodes_per_group * events_per_node

Subscribers

People subscribed via source and target branches