Merge lp:~allenap/maas/fewer-power-params-per-batch into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Andres Rodriguez
Approved revision: no longer in the source branch.
Merged at revision: 5948
Proposed branch: lp:~allenap/maas/fewer-power-params-per-batch
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 148 lines (+51/-5)
4 files modified
src/maasserver/rpc/nodes.py (+10/-2)
src/maasserver/rpc/tests/test_nodes.py (+24/-3)
src/maasserver/testing/factory.py (+1/-0)
src/maasserver/testing/tests/test_factory.py (+16/-0)
To merge this branch: bzr merge lp:~allenap/maas/fewer-power-params-per-batch
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+322089@code.launchpad.net

Commit message

Limit batches of node power parameters to 10 for the periodic power checking service.

This is intended to reduce the likelihood of transactional conflicts.

Description of the change

When each batch of node power parameters is fetched from the region by a rack's power monitor service there is a sting in the tail: every row in maasserver_node that corresponds to those parameters is updated. This appears to be increasing transactional conflicts in the database to the point where it exceeds 10 retries and the transaction is fully rejected.

A better way to fix this would be to not update those rows, or to have a separate table to record last-queried times, but that's a lot more work and there isn't time right now, so instead we limit the batch size to 10.

This also fixes a long-standing bug in Factory.make_Node where the given power parameters were being ignored when also using bmc_connected_to. This fix improves the speed of one particular test, taking it from ~15 seconds to <1 second.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm! Should this be attached to the bug report ?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/rpc/nodes.py'
2--- src/maasserver/rpc/nodes.py 2017-01-28 00:51:47 +0000
3+++ src/maasserver/rpc/nodes.py 2017-04-06 08:23:19 +0000
4@@ -11,7 +11,10 @@
5 ]
6
7 from datetime import timedelta
8-from itertools import chain
9+from itertools import (
10+ chain,
11+ islice,
12+)
13 import json
14
15 from django.contrib.auth.models import User
16@@ -135,11 +138,15 @@
17
18 @synchronous
19 @transactional
20-def list_cluster_nodes_power_parameters(system_id):
21+def list_cluster_nodes_power_parameters(system_id, limit=10):
22 """Return power parameters that a rack controller should power check,
23 in priority order.
24
25 For :py:class:`~provisioningserver.rpc.region.ListNodePowerParameters`.
26+
27+ :param limit: Limit the number of nodes for which to return power
28+ parameters. Pass `None` to remove this numerical limit; there is still
29+ a limit on the quantity of power information that will be returned.
30 """
31 try:
32 rack = RackController.objects.get(system_id=system_id)
33@@ -149,6 +156,7 @@
34 # Generate all the the power queries that will fit into the response.
35 nodes = rack.get_bmc_accessible_nodes()
36 details = _gen_cluster_nodes_power_parameters(nodes)
37+ details = islice(details, limit) # ... but never more than `limit`.
38 details = _gen_up_to_json_limit(details, 60 * (2 ** 10)) # 60kiB
39 details = list(details)
40
41
42=== modified file 'src/maasserver/rpc/tests/test_nodes.py'
43--- src/maasserver/rpc/tests/test_nodes.py 2017-03-31 22:57:48 +0000
44+++ src/maasserver/rpc/tests/test_nodes.py 2017-04-06 08:23:19 +0000
45@@ -61,6 +61,7 @@
46 from testtools.matchers import (
47 Equals,
48 GreaterThan,
49+ HasLength,
50 Is,
51 LessThan,
52 Not,
53@@ -469,7 +470,7 @@
54 system_ids)
55
56 def test__returns_at_most_60kiB_of_JSON(self):
57- # Configure the rack controller subnt to be very large so it
58+ # Configure the rack controller subnet to be very large so it
59 # can hold that many BMC connected to the interface for the rack
60 # controller.
61 rack = factory.make_RackController(power_type='')
62@@ -482,14 +483,15 @@
63
64 # Ensure that there are at least 64kiB of power parameters (when
65 # converted to JSON) in the database.
66- example_parameters = {"key%d" % i: "value%d" % i for i in range(100)}
67+ example_parameters = {"key%d" % i: "value%d" % i for i in range(250)}
68 remaining = 2 ** 16
69 while remaining > 0:
70 node = self.make_Node(
71 bmc_connected_to=rack, power_parameters=example_parameters)
72 remaining -= len(json.dumps(node.get_effective_power_parameters()))
73
74- nodes = list_cluster_nodes_power_parameters(rack.system_id)
75+ nodes = list_cluster_nodes_power_parameters(
76+ rack.system_id, limit=None) # Remove numeric limit.
77
78 # The total size of the JSON is less than 60kiB, but only a bit.
79 nodes_json = map(json.dumps, nodes)
80@@ -500,6 +502,25 @@
81 expected_minimum = 50 * (2 ** 10) # 50kiB
82 self.expectThat(nodes_json_length, GreaterThan(expected_minimum - 1))
83
84+ def test__limited_to_10_nodes_at_a_time_by_default(self):
85+ # Configure the rack controller subnet to be large enough.
86+ rack = factory.make_RackController(power_type='')
87+ rack_interface = rack.get_boot_interface()
88+ subnet = factory.make_Subnet(
89+ cidr=str(factory.make_ipv6_network(slash=8)))
90+ factory.make_StaticIPAddress(
91+ ip=factory.pick_ip_in_Subnet(subnet), subnet=subnet,
92+ interface=rack_interface)
93+
94+ # Create at least 11 nodes connected to the rack.
95+ for _ in range(11):
96+ self.make_Node(bmc_connected_to=rack)
97+
98+ # Only 10 nodes' power parameters are returned.
99+ self.assertThat(
100+ list_cluster_nodes_power_parameters(rack.system_id),
101+ HasLength(10))
102+
103
104 class TestUpdateNodePowerState(MAASServerTestCase):
105
106
107=== modified file 'src/maasserver/testing/factory.py'
108--- src/maasserver/testing/factory.py 2017-04-03 19:46:59 +0000
109+++ src/maasserver/testing/factory.py 2017-04-06 08:23:19 +0000
110@@ -456,6 +456,7 @@
111 ip_address = existing_static_ips[0]
112 bmc_ip_address = self.pick_ip_in_Subnet(ip_address.subnet)
113 node.power_parameters = {
114+ **node.power_parameters,
115 "power_address": "qemu+ssh://user@%s/system" % (
116 factory.ip_to_url_format(bmc_ip_address)),
117 "power_id": factory.make_name("power_id"),
118
119=== modified file 'src/maasserver/testing/tests/test_factory.py'
120--- src/maasserver/testing/tests/test_factory.py 2016-09-22 02:53:33 +0000
121+++ src/maasserver/testing/tests/test_factory.py 2017-04-06 08:23:19 +0000
122@@ -12,6 +12,7 @@
123 from maasserver.utils.orm import reload_object
124 from testtools.matchers import (
125 Contains,
126+ ContainsDict,
127 Equals,
128 )
129
130@@ -99,3 +100,18 @@
131 self.assertThat(
132 {iface.vlan for iface in sip.interface_set.all()},
133 Contains(iface.vlan))
134+
135+
136+class TestFactoryForNodes(MAASServerTestCase):
137+
138+ def test_make_Node_uses_power_parameters_when_connecting_to_BMC(self):
139+ p_key = factory.make_name("key")
140+ p_value = factory.make_name("value")
141+ node = factory.make_Node(
142+ bmc_connected_to=factory.make_RackController(),
143+ power_parameters={p_key: p_value},
144+ )
145+ self.assertThat(
146+ node.get_effective_power_parameters(),
147+ ContainsDict({p_key: Equals(p_value)}),
148+ )