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
diff --git a/src/maasserver/api/events.py b/src/maasserver/api/events.py
index 72ff4f1..eee116d 100644
--- a/src/maasserver/api/events.py
+++ b/src/maasserver/api/events.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__all__ = [4__all__ = [
@@ -37,8 +37,15 @@ DEFAULT_EVENT_LOG_LIMIT = 100
37def event_to_dict(event):37def event_to_dict(event):
38 """Convert `Event` to a dictionary."""38 """Convert `Event` to a dictionary."""
39 return dict(39 return dict(
40 node=event.node.system_id,40 username=(
41 hostname=event.node.hostname,41 event.user.username
42 if event.user is not None else event.username),
43 node=(
44 event.node.system_id
45 if event.node is not None else None),
46 hostname=(
47 event.node.hostname
48 if event.node is not None else event.node_hostname),
42 id=event.id,49 id=event.id,
43 level=event.type.level_str,50 level=event.type.level_str,
44 created=event.created.strftime('%a, %d %b. %Y %H:%M:%S'),51 created=event.created.strftime('%a, %d %b. %Y %H:%M:%S'),
@@ -133,20 +140,21 @@ class EventsHandler(OperationsHandler):
133 # Check first for AUDIT level.140 # Check first for AUDIT level.
134 if level == LOGGING_LEVELS[AUDIT]:141 if level == LOGGING_LEVELS[AUDIT]:
135 events = Event.objects.filter(type__level=AUDIT)142 events = Event.objects.filter(type__level=AUDIT)
136 events = (events.all().prefetch_related('type'))
137 elif level in LOGGING_LEVELS_BY_NAME:143 elif level in LOGGING_LEVELS_BY_NAME:
138 events = Event.objects.filter(node__in=nodes)144 events = Event.objects.filter(node__in=nodes)
139 # Eliminate logs below the requested level.145 # Eliminate logs below the requested level.
140 events = events.exclude(146 events = events.exclude(
141 type__level__lt=LOGGING_LEVELS_BY_NAME[level])147 type__level__lt=LOGGING_LEVELS_BY_NAME[level])
142 events = (
143 events.all()
144 .prefetch_related('type')
145 .prefetch_related('node'))
146 elif level is not None:148 elif level is not None:
147 raise MAASAPIBadRequest(149 raise MAASAPIBadRequest(
148 "Unrecognised log level: %s" % level)150 "Unrecognised log level: %s" % level)
149151
152 events = (
153 events.all()
154 .select_related('type')
155 .select_related('node')
156 .select_related('user'))
157
150 # Filter events for owner.158 # Filter events for owner.
151 if owner is not None:159 if owner is not None:
152 events = events.filter(user__username=owner)160 events = events.filter(user__username=owner)
diff --git a/src/maasserver/api/tests/test_events.py b/src/maasserver/api/tests/test_events.py
index 2cbbf12..24ecff1 100644
--- a/src/maasserver/api/tests/test_events.py
+++ b/src/maasserver/api/tests/test_events.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for the events API."""4"""Tests for the events API."""
@@ -20,6 +20,7 @@ from urllib.parse import (
2020
21from django.conf import settings21from django.conf import settings
22from maasserver.api import events as events_module22from maasserver.api import events as events_module
23from maasserver.api.events import event_to_dict
23from maasserver.api.tests.test_nodes import RequestFixture24from maasserver.api.tests.test_nodes import RequestFixture
24from maasserver.enum import NODE_TYPE25from maasserver.enum import NODE_TYPE
25from maasserver.testing.api import APITestCase26from maasserver.testing.api import APITestCase
@@ -36,6 +37,7 @@ from testtools.matchers import (
36 Equals,37 Equals,
37 HasLength,38 HasLength,
38 Is,39 Is,
40 MatchesDict,
39 MatchesStructure,41 MatchesStructure,
40 Not,42 Not,
41)43)
@@ -71,6 +73,54 @@ def AfterBeingDecoded(matcher):
71 matcher)73 matcher)
7274
7375
76class TestEventToDict(APITestCase.ForUser):
77 """Test for `event_to_dict` function."""
78
79 def test__node_not_None(self):
80 event = factory.make_Event()
81 self.assertThat(event_to_dict(event), MatchesDict({
82 "username": Equals(event.user.username),
83 "node": Equals(event.node.system_id),
84 "hostname": Equals(event.node.hostname),
85 "id": Equals(event.id),
86 "level": Equals(event.type.level_str),
87 "created": Equals(
88 event.created.strftime('%a, %d %b. %Y %H:%M:%S')),
89 "type": Equals(event.type.description),
90 "description": Equals(event.description),
91 }))
92
93 def test__node_and_user_is_None(self):
94 event = factory.make_Event()
95 event.node = None
96 event.user = None
97 self.assertThat(event_to_dict(event), MatchesDict({
98 "username": Equals(event.username),
99 "node": Equals(None),
100 "hostname": Equals(event.node_hostname),
101 "id": Equals(event.id),
102 "level": Equals(event.type.level_str),
103 "created": Equals(
104 event.created.strftime('%a, %d %b. %Y %H:%M:%S')),
105 "type": Equals(event.type.description),
106 "description": Equals(event.description),
107 }))
108
109 def test__type_level_AUDIT(self):
110 event = factory.make_Event()
111 self.assertThat(event_to_dict(event), MatchesDict({
112 "username": Equals(event.user.username),
113 "node": Equals(event.node.system_id),
114 "hostname": Equals(event.node.hostname),
115 "id": Equals(event.id),
116 "level": Equals(event.type.level_str),
117 "created": Equals(
118 event.created.strftime('%a, %d %b. %Y %H:%M:%S')),
119 "type": Equals(event.type.description),
120 "description": Equals(event.render_audit_description),
121 }))
122
123
74class TestEventsAPI(APITestCase.ForUser):124class TestEventsAPI(APITestCase.ForUser):
75 """Tests for /api/2.0/events/."""125 """Tests for /api/2.0/events/."""
76126
@@ -588,10 +638,8 @@ class TestEventsAPI(APITestCase.ForUser):
588 make_events(number_events, node=node)638 make_events(number_events, node=node)
589639
590 def test_query_num_queries_is_independent_of_num_nodes_and_events(self):640 def test_query_num_queries_is_independent_of_num_nodes_and_events(self):
591 # 1 query for select event +641 # 1 query for all the select_related's.
592 # 1 query to prefetch eventtype +642 expected_queries = 1
593 # 1 query to prefetch node details
594 expected_queries = 3
595 events_per_node = 5643 events_per_node = 5
596 num_nodes_per_group = 5644 num_nodes_per_group = 5
597 events_per_group = num_nodes_per_group * events_per_node645 events_per_group = num_nodes_per_group * events_per_node

Subscribers

People subscribed via source and target branches