Merge ~bjornt/maas:brownfield-metrics into maas:master

Proposed by Björn Tillenius
Status: Merged
Approved by: Björn Tillenius
Approved revision: 2fa90f2978ae440eb9d9e024d94ed9fc06b436ad
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~bjornt/maas:brownfield-metrics
Merge into: maas:master
Diff against target: 252 lines (+169/-1)
2 files modified
src/maasserver/stats.py (+42/-1)
src/maasserver/tests/test_stats.py (+127/-0)
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
MAAS Lander Approve
Review via email: mp+408819@code.launchpad.net

Commit message

Add stats for brownfield machines.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b brownfield-metrics lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 2fa90f2978ae440eb9d9e024d94ed9fc06b436ad

review: Approve
Revision history for this message
Alberto Donato (ack) :
Revision history for this message
Alberto Donato (ack) wrote :

+1 with a couple of suggestions inline

review: Approve
Revision history for this message
Björn Tillenius (bjornt) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/stats.py b/src/maasserver/stats.py
2index 36b9fd6..91f7340 100644
3--- a/src/maasserver/stats.py
4+++ b/src/maasserver/stats.py
5@@ -14,7 +14,7 @@ from collections import Counter, defaultdict
6 from datetime import timedelta
7 import json
8
9-from django.db.models import Count, F
10+from django.db.models import Count, F, Max
11 import requests
12 from twisted.application.internet import TimerService
13
14@@ -41,7 +41,9 @@ from maasserver.models.virtualmachine import get_vm_host_used_resources
15 from maasserver.utils import get_maas_user_agent
16 from maasserver.utils.orm import NotNullSum, transactional
17 from maasserver.utils.threads import deferToDatabase
18+from metadataserver.enum import SCRIPT_STATUS
19 from provisioningserver.logger import LegacyLogger
20+from provisioningserver.refresh.node_info_scripts import LXD_OUTPUT_NAME
21 from provisioningserver.utils.network import IPRangeStatistics
22
23 log = LegacyLogger()
24@@ -227,6 +229,44 @@ def get_custom_images_deployed_stats():
25 return Machine.objects.filter(osystem="custom").count()
26
27
28+def get_brownfield_stats():
29+ deployed_machines = Machine.objects.filter(
30+ dynamic=False,
31+ status=NODE_STATUS.DEPLOYED,
32+ )
33+ brownfield_machines = deployed_machines.filter(
34+ current_installation_script_set__isnull=True,
35+ )
36+ no_brownfield_machines = Machine.objects.filter(
37+ current_installation_script_set__isnull=False,
38+ ).annotate(
39+ latest_installation_script_date=Max(
40+ "current_installation_script_set__scriptresult__updated"
41+ ),
42+ latest_commissioning_script_date=Max(
43+ "current_commissioning_script_set__scriptresult__updated"
44+ ),
45+ )
46+
47+ return {
48+ "machines_added_deployed_with_bmc": brownfield_machines.filter(
49+ bmc__isnull=False
50+ ).count(),
51+ "machines_added_deployed_without_bmc": brownfield_machines.filter(
52+ bmc__isnull=True
53+ ).count(),
54+ "commissioned_after_deploy_brownfield": brownfield_machines.filter(
55+ current_commissioning_script_set__scriptresult__script_name=LXD_OUTPUT_NAME,
56+ current_commissioning_script_set__scriptresult__status=SCRIPT_STATUS.PASSED,
57+ ).count(),
58+ "commissioned_after_deploy_no_brownfield": no_brownfield_machines.filter(
59+ latest_commissioning_script_date__gt=F(
60+ "latest_installation_script_date"
61+ ),
62+ ).count(),
63+ }
64+
65+
66 def get_maas_stats():
67 # TODO
68 # - architectures
69@@ -261,6 +301,7 @@ def get_maas_stats():
70 "virsh": get_vm_hosts_stats(power_type="virsh"),
71 },
72 "workload_annotations": get_workload_annotations_stats(),
73+ "brownfield": get_brownfield_stats(),
74 }
75
76
77diff --git a/src/maasserver/tests/test_stats.py b/src/maasserver/tests/test_stats.py
78index 7d6bb85..39f4942 100644
79--- a/src/maasserver/tests/test_stats.py
80+++ b/src/maasserver/tests/test_stats.py
81@@ -14,16 +14,19 @@ from twisted.internet.defer import fail
82
83 from maasserver import stats
84 from maasserver.enum import IPADDRESS_TYPE, IPRANGE_TYPE, NODE_STATUS
85+from maasserver.forms import AdminMachineForm
86 from maasserver.models import (
87 BootResourceFile,
88 Config,
89 Fabric,
90+ Machine,
91 OwnerData,
92 Space,
93 Subnet,
94 VLAN,
95 )
96 from maasserver.stats import (
97+ get_brownfield_stats,
98 get_custom_images_deployed_stats,
99 get_custom_images_uploaded_stats,
100 get_maas_stats,
101@@ -42,6 +45,12 @@ from maasserver.testing.testcase import (
102 from maastesting.matchers import MockCalledOnce, MockNotCalled
103 from maastesting.testcase import MAASTestCase
104 from maastesting.twisted import extract_result
105+from metadataserver.builtin_scripts import load_builtin_scripts
106+from metadataserver.enum import RESULT_TYPE, SCRIPT_STATUS
107+from metadataserver.models.scriptresult import ScriptResult
108+from metadataserver.models.scriptset import ScriptSet
109+from provisioningserver.drivers.pod import DiscoveredPod
110+from provisioningserver.refresh.node_info_scripts import LXD_OUTPUT_NAME
111 from provisioningserver.utils.twisted import asynchronous
112
113
114@@ -289,6 +298,12 @@ class TestMAASStats(MAASServerTestCase):
115 "unique_keys": 1,
116 "unique_values": 1,
117 },
118+ "brownfield": {
119+ "machines_added_deployed_with_bmc": 2,
120+ "machines_added_deployed_without_bmc": 0,
121+ "commissioned_after_deploy_brownfield": 0,
122+ "commissioned_after_deploy_no_brownfield": 0,
123+ },
124 }
125 self.assertEqual(stats, expected)
126
127@@ -393,6 +408,12 @@ class TestMAASStats(MAASServerTestCase):
128 "unique_keys": 0,
129 "unique_values": 0,
130 },
131+ "brownfield": {
132+ "machines_added_deployed_with_bmc": 0,
133+ "machines_added_deployed_without_bmc": 0,
134+ "commissioned_after_deploy_brownfield": 0,
135+ "commissioned_after_deploy_no_brownfield": 0,
136+ },
137 }
138 self.assertEqual(get_maas_stats(), expected)
139
140@@ -444,6 +465,112 @@ class TestMAASStats(MAASServerTestCase):
141 self.assertEqual(get_custom_images_deployed_stats(), 2)
142
143
144+class FakeRequest:
145+ def __init__(self, user):
146+ self.user = user
147+
148+
149+class TestGetBrownfieldStats(MAASServerTestCase):
150+ def setUp(self):
151+ super().setUp()
152+ load_builtin_scripts()
153+
154+ def _make_brownfield_machine(self):
155+ admin = factory.make_admin()
156+ # Use the form to create the brownfield node, so that it gets
157+ # created in the same way as in a real MAAS deployement.
158+ form = AdminMachineForm(
159+ request=FakeRequest(admin),
160+ data={
161+ "hostname": factory.make_string(),
162+ "deployed": True,
163+ },
164+ )
165+ return form.save()
166+
167+ def _make_normal_deployed_machine(self):
168+ machine = factory.make_Machine(
169+ status=NODE_STATUS.DEPLOYED, previous_status=NODE_STATUS.DEPLOYING
170+ )
171+ machine.current_commissioning_script_set = (
172+ ScriptSet.objects.create_commissioning_script_set(machine)
173+ )
174+ machine.current_installation_script_set = factory.make_ScriptSet(
175+ node=machine, result_type=RESULT_TYPE.INSTALLATION
176+ )
177+ factory.make_ScriptResult(
178+ script_set=machine.current_installation_script_set,
179+ status=SCRIPT_STATUS.PASSED,
180+ exit_status=0,
181+ )
182+ machine.save()
183+ return machine
184+
185+ def _make_pod_machine(self):
186+ factory.make_usable_boot_resource(architecture="amd64/generic")
187+ pod = factory.make_Pod()
188+ mac_addresses = [factory.make_mac_address() for _ in range(3)]
189+ sync_user = factory.make_User()
190+ return pod.sync(
191+ DiscoveredPod(
192+ architectures=["amd64/generic"], mac_addresses=mac_addresses
193+ ),
194+ sync_user,
195+ )
196+
197+ def _update_commissioning(self, machine):
198+ commissioning_result = ScriptResult.objects.get(
199+ script_set=machine.current_commissioning_script_set,
200+ script_name=LXD_OUTPUT_NAME,
201+ )
202+ commissioning_result.store_result(exit_status=0)
203+
204+ def test_added_deployed(self):
205+ for _ in range(5):
206+ machine = self._make_brownfield_machine()
207+ machine.bmc = factory.make_BMC()
208+ machine.save()
209+ for _ in range(9):
210+ machine = self._make_brownfield_machine()
211+ machine.bmc = None
212+ machine.save()
213+ normal = self._make_normal_deployed_machine()
214+ factory.make_Machine(status=NODE_STATUS.READY)
215+ # If pods and controllers are registered in MAAS, that don't
216+ # have a corresponding machine already, MAAS will basically
217+ # create them as brownfield nodes. We don't want those included
218+ # in the stats.
219+ pod = self._make_pod_machine()
220+ controller = factory.make_Controller()
221+ brownfield_machines = Machine.objects.filter(
222+ current_installation_script_set__isnull=True,
223+ dynamic=False,
224+ ).all()
225+ self.assertNotIn(normal, brownfield_machines)
226+ self.assertNotIn(controller, brownfield_machines)
227+ self.assertNotIn(pod, brownfield_machines)
228+ stats = get_brownfield_stats()
229+ self.assertEqual(5, stats["machines_added_deployed_with_bmc"])
230+ self.assertEqual(9, stats["machines_added_deployed_without_bmc"])
231+
232+ def test_commission_after_deploy_brownfield(self):
233+ for _ in range(5):
234+ self._update_commissioning(self._make_brownfield_machine())
235+ self._make_brownfield_machine()
236+ for _ in range(9):
237+ self._update_commissioning(self._make_normal_deployed_machine())
238+ self._make_normal_deployed_machine()
239+ # If pods and controllers are registered in MAAS, that don't
240+ # have a corresponding machine already, MAAS will basically
241+ # create them as brownfield nodes. We don't want those included
242+ # in the stats.
243+ self._make_pod_machine()
244+ factory.make_Controller()
245+ stats = get_brownfield_stats()
246+ self.assertEqual(5, stats["commissioned_after_deploy_brownfield"])
247+ self.assertEqual(9, stats["commissioned_after_deploy_no_brownfield"])
248+
249+
250 class TestGetSubnetsUtilisationStats(MAASServerTestCase):
251 def test_stats_totals(self):
252 factory.make_Subnet(cidr="1.2.0.0/16", gateway_ip="1.2.0.254")

Subscribers

People subscribed via source and target branches