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
diff --git a/src/maasserver/stats.py b/src/maasserver/stats.py
index 36b9fd6..91f7340 100644
--- a/src/maasserver/stats.py
+++ b/src/maasserver/stats.py
@@ -14,7 +14,7 @@ from collections import Counter, defaultdict
14from datetime import timedelta14from datetime import timedelta
15import json15import json
1616
17from django.db.models import Count, F17from django.db.models import Count, F, Max
18import requests18import requests
19from twisted.application.internet import TimerService19from twisted.application.internet import TimerService
2020
@@ -41,7 +41,9 @@ from maasserver.models.virtualmachine import get_vm_host_used_resources
41from maasserver.utils import get_maas_user_agent41from maasserver.utils import get_maas_user_agent
42from maasserver.utils.orm import NotNullSum, transactional42from maasserver.utils.orm import NotNullSum, transactional
43from maasserver.utils.threads import deferToDatabase43from maasserver.utils.threads import deferToDatabase
44from metadataserver.enum import SCRIPT_STATUS
44from provisioningserver.logger import LegacyLogger45from provisioningserver.logger import LegacyLogger
46from provisioningserver.refresh.node_info_scripts import LXD_OUTPUT_NAME
45from provisioningserver.utils.network import IPRangeStatistics47from provisioningserver.utils.network import IPRangeStatistics
4648
47log = LegacyLogger()49log = LegacyLogger()
@@ -227,6 +229,44 @@ def get_custom_images_deployed_stats():
227 return Machine.objects.filter(osystem="custom").count()229 return Machine.objects.filter(osystem="custom").count()
228230
229231
232def get_brownfield_stats():
233 deployed_machines = Machine.objects.filter(
234 dynamic=False,
235 status=NODE_STATUS.DEPLOYED,
236 )
237 brownfield_machines = deployed_machines.filter(
238 current_installation_script_set__isnull=True,
239 )
240 no_brownfield_machines = Machine.objects.filter(
241 current_installation_script_set__isnull=False,
242 ).annotate(
243 latest_installation_script_date=Max(
244 "current_installation_script_set__scriptresult__updated"
245 ),
246 latest_commissioning_script_date=Max(
247 "current_commissioning_script_set__scriptresult__updated"
248 ),
249 )
250
251 return {
252 "machines_added_deployed_with_bmc": brownfield_machines.filter(
253 bmc__isnull=False
254 ).count(),
255 "machines_added_deployed_without_bmc": brownfield_machines.filter(
256 bmc__isnull=True
257 ).count(),
258 "commissioned_after_deploy_brownfield": brownfield_machines.filter(
259 current_commissioning_script_set__scriptresult__script_name=LXD_OUTPUT_NAME,
260 current_commissioning_script_set__scriptresult__status=SCRIPT_STATUS.PASSED,
261 ).count(),
262 "commissioned_after_deploy_no_brownfield": no_brownfield_machines.filter(
263 latest_commissioning_script_date__gt=F(
264 "latest_installation_script_date"
265 ),
266 ).count(),
267 }
268
269
230def get_maas_stats():270def get_maas_stats():
231 # TODO271 # TODO
232 # - architectures272 # - architectures
@@ -261,6 +301,7 @@ def get_maas_stats():
261 "virsh": get_vm_hosts_stats(power_type="virsh"),301 "virsh": get_vm_hosts_stats(power_type="virsh"),
262 },302 },
263 "workload_annotations": get_workload_annotations_stats(),303 "workload_annotations": get_workload_annotations_stats(),
304 "brownfield": get_brownfield_stats(),
264 }305 }
265306
266307
diff --git a/src/maasserver/tests/test_stats.py b/src/maasserver/tests/test_stats.py
index 7d6bb85..39f4942 100644
--- a/src/maasserver/tests/test_stats.py
+++ b/src/maasserver/tests/test_stats.py
@@ -14,16 +14,19 @@ from twisted.internet.defer import fail
1414
15from maasserver import stats15from maasserver import stats
16from maasserver.enum import IPADDRESS_TYPE, IPRANGE_TYPE, NODE_STATUS16from maasserver.enum import IPADDRESS_TYPE, IPRANGE_TYPE, NODE_STATUS
17from maasserver.forms import AdminMachineForm
17from maasserver.models import (18from maasserver.models import (
18 BootResourceFile,19 BootResourceFile,
19 Config,20 Config,
20 Fabric,21 Fabric,
22 Machine,
21 OwnerData,23 OwnerData,
22 Space,24 Space,
23 Subnet,25 Subnet,
24 VLAN,26 VLAN,
25)27)
26from maasserver.stats import (28from maasserver.stats import (
29 get_brownfield_stats,
27 get_custom_images_deployed_stats,30 get_custom_images_deployed_stats,
28 get_custom_images_uploaded_stats,31 get_custom_images_uploaded_stats,
29 get_maas_stats,32 get_maas_stats,
@@ -42,6 +45,12 @@ from maasserver.testing.testcase import (
42from maastesting.matchers import MockCalledOnce, MockNotCalled45from maastesting.matchers import MockCalledOnce, MockNotCalled
43from maastesting.testcase import MAASTestCase46from maastesting.testcase import MAASTestCase
44from maastesting.twisted import extract_result47from maastesting.twisted import extract_result
48from metadataserver.builtin_scripts import load_builtin_scripts
49from metadataserver.enum import RESULT_TYPE, SCRIPT_STATUS
50from metadataserver.models.scriptresult import ScriptResult
51from metadataserver.models.scriptset import ScriptSet
52from provisioningserver.drivers.pod import DiscoveredPod
53from provisioningserver.refresh.node_info_scripts import LXD_OUTPUT_NAME
45from provisioningserver.utils.twisted import asynchronous54from provisioningserver.utils.twisted import asynchronous
4655
4756
@@ -289,6 +298,12 @@ class TestMAASStats(MAASServerTestCase):
289 "unique_keys": 1,298 "unique_keys": 1,
290 "unique_values": 1,299 "unique_values": 1,
291 },300 },
301 "brownfield": {
302 "machines_added_deployed_with_bmc": 2,
303 "machines_added_deployed_without_bmc": 0,
304 "commissioned_after_deploy_brownfield": 0,
305 "commissioned_after_deploy_no_brownfield": 0,
306 },
292 }307 }
293 self.assertEqual(stats, expected)308 self.assertEqual(stats, expected)
294309
@@ -393,6 +408,12 @@ class TestMAASStats(MAASServerTestCase):
393 "unique_keys": 0,408 "unique_keys": 0,
394 "unique_values": 0,409 "unique_values": 0,
395 },410 },
411 "brownfield": {
412 "machines_added_deployed_with_bmc": 0,
413 "machines_added_deployed_without_bmc": 0,
414 "commissioned_after_deploy_brownfield": 0,
415 "commissioned_after_deploy_no_brownfield": 0,
416 },
396 }417 }
397 self.assertEqual(get_maas_stats(), expected)418 self.assertEqual(get_maas_stats(), expected)
398419
@@ -444,6 +465,112 @@ class TestMAASStats(MAASServerTestCase):
444 self.assertEqual(get_custom_images_deployed_stats(), 2)465 self.assertEqual(get_custom_images_deployed_stats(), 2)
445466
446467
468class FakeRequest:
469 def __init__(self, user):
470 self.user = user
471
472
473class TestGetBrownfieldStats(MAASServerTestCase):
474 def setUp(self):
475 super().setUp()
476 load_builtin_scripts()
477
478 def _make_brownfield_machine(self):
479 admin = factory.make_admin()
480 # Use the form to create the brownfield node, so that it gets
481 # created in the same way as in a real MAAS deployement.
482 form = AdminMachineForm(
483 request=FakeRequest(admin),
484 data={
485 "hostname": factory.make_string(),
486 "deployed": True,
487 },
488 )
489 return form.save()
490
491 def _make_normal_deployed_machine(self):
492 machine = factory.make_Machine(
493 status=NODE_STATUS.DEPLOYED, previous_status=NODE_STATUS.DEPLOYING
494 )
495 machine.current_commissioning_script_set = (
496 ScriptSet.objects.create_commissioning_script_set(machine)
497 )
498 machine.current_installation_script_set = factory.make_ScriptSet(
499 node=machine, result_type=RESULT_TYPE.INSTALLATION
500 )
501 factory.make_ScriptResult(
502 script_set=machine.current_installation_script_set,
503 status=SCRIPT_STATUS.PASSED,
504 exit_status=0,
505 )
506 machine.save()
507 return machine
508
509 def _make_pod_machine(self):
510 factory.make_usable_boot_resource(architecture="amd64/generic")
511 pod = factory.make_Pod()
512 mac_addresses = [factory.make_mac_address() for _ in range(3)]
513 sync_user = factory.make_User()
514 return pod.sync(
515 DiscoveredPod(
516 architectures=["amd64/generic"], mac_addresses=mac_addresses
517 ),
518 sync_user,
519 )
520
521 def _update_commissioning(self, machine):
522 commissioning_result = ScriptResult.objects.get(
523 script_set=machine.current_commissioning_script_set,
524 script_name=LXD_OUTPUT_NAME,
525 )
526 commissioning_result.store_result(exit_status=0)
527
528 def test_added_deployed(self):
529 for _ in range(5):
530 machine = self._make_brownfield_machine()
531 machine.bmc = factory.make_BMC()
532 machine.save()
533 for _ in range(9):
534 machine = self._make_brownfield_machine()
535 machine.bmc = None
536 machine.save()
537 normal = self._make_normal_deployed_machine()
538 factory.make_Machine(status=NODE_STATUS.READY)
539 # If pods and controllers are registered in MAAS, that don't
540 # have a corresponding machine already, MAAS will basically
541 # create them as brownfield nodes. We don't want those included
542 # in the stats.
543 pod = self._make_pod_machine()
544 controller = factory.make_Controller()
545 brownfield_machines = Machine.objects.filter(
546 current_installation_script_set__isnull=True,
547 dynamic=False,
548 ).all()
549 self.assertNotIn(normal, brownfield_machines)
550 self.assertNotIn(controller, brownfield_machines)
551 self.assertNotIn(pod, brownfield_machines)
552 stats = get_brownfield_stats()
553 self.assertEqual(5, stats["machines_added_deployed_with_bmc"])
554 self.assertEqual(9, stats["machines_added_deployed_without_bmc"])
555
556 def test_commission_after_deploy_brownfield(self):
557 for _ in range(5):
558 self._update_commissioning(self._make_brownfield_machine())
559 self._make_brownfield_machine()
560 for _ in range(9):
561 self._update_commissioning(self._make_normal_deployed_machine())
562 self._make_normal_deployed_machine()
563 # If pods and controllers are registered in MAAS, that don't
564 # have a corresponding machine already, MAAS will basically
565 # create them as brownfield nodes. We don't want those included
566 # in the stats.
567 self._make_pod_machine()
568 factory.make_Controller()
569 stats = get_brownfield_stats()
570 self.assertEqual(5, stats["commissioned_after_deploy_brownfield"])
571 self.assertEqual(9, stats["commissioned_after_deploy_no_brownfield"])
572
573
447class TestGetSubnetsUtilisationStats(MAASServerTestCase):574class TestGetSubnetsUtilisationStats(MAASServerTestCase):
448 def test_stats_totals(self):575 def test_stats_totals(self):
449 factory.make_Subnet(cidr="1.2.0.0/16", gateway_ip="1.2.0.254")576 factory.make_Subnet(cidr="1.2.0.0/16", gateway_ip="1.2.0.254")

Subscribers

People subscribed via source and target branches