Merge lp:~mpontillo/maas/fix-1517097-1.9 into lp:maas/1.9

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 4506
Proposed branch: lp:~mpontillo/maas/fix-1517097-1.9
Merge into: lp:maas/1.9
Diff against target: 139 lines (+57/-17)
3 files modified
src/maasserver/node_constraint_filter_forms.py (+2/-9)
src/maasserver/tests/test_node_constraint_filter_forms.py (+25/-1)
src/maasserver/utils/orm.py (+30/-7)
To merge this branch: bzr merge lp:~mpontillo/maas/fix-1517097-1.9
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+278231@code.launchpad.net

This proposal supersedes a proposal from 2015-11-21.

Commit message

Merge fix for #1517097 from trunk revision 4512.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Self-approving approved backported fix.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/node_constraint_filter_forms.py'
2--- src/maasserver/node_constraint_filter_forms.py 2015-11-03 02:37:13 +0000
3+++ src/maasserver/node_constraint_filter_forms.py 2015-11-21 00:08:55 +0000
4@@ -398,19 +398,12 @@
5 node_ids = None
6 label_map = {}
7 for label in interfaces_label_map:
8- specifiers = []
9 constraints = interfaces_label_map[label]
10- for specifier_type in constraints.iterkeys():
11- # Build constraints string.
12- specifiers.extend([
13- specifier_type + ":" + item
14- for item in constraints[specifier_type]
15- ])
16 if node_ids is None:
17 # The first time through the filter, build the list
18 # of candidate nodes.
19 node_ids, node_map = Interface.objects.get_matching_node_map(
20- specifiers)
21+ constraints)
22 label_map[label] = node_map
23 else:
24 # For subsequent labels, only match nodes that already matched a
25@@ -421,7 +414,7 @@
26 # to filter the nodes starting from an 'id__in' filter using the
27 # current 'node_ids' set.
28 new_node_ids, node_map = Interface.objects.get_matching_node_map(
29- specifiers)
30+ constraints)
31 label_map[label] = node_map
32 node_ids &= new_node_ids
33 return node_ids, label_map
34
35=== modified file 'src/maasserver/tests/test_node_constraint_filter_forms.py'
36--- src/maasserver/tests/test_node_constraint_filter_forms.py 2015-11-03 02:37:13 +0000
37+++ src/maasserver/tests/test_node_constraint_filter_forms.py 2015-11-21 00:08:55 +0000
38@@ -1026,7 +1026,7 @@
39 filtered_nodes, _, _ = form.filter_nodes(Node.nodes)
40 self.assertItemsEqual([node2], filtered_nodes)
41
42- def test_interfaces_filters_multiples_treated_as_OR_operation(self):
43+ def test_interfaces_filters_same_key_treated_as_OR_operation(self):
44 fabric1 = factory.make_Fabric(class_type="1g")
45 fabric2 = factory.make_Fabric(class_type="10g")
46 vlan1 = factory.make_VLAN(vid=1, fabric=fabric1)
47@@ -1050,6 +1050,30 @@
48 filtered_nodes, _, _ = form.filter_nodes(Node.nodes)
49 self.assertItemsEqual([node2], filtered_nodes)
50
51+ def test_interfaces_filters_different_key_treated_as_AND_operation(self):
52+ fabric1 = factory.make_Fabric(class_type="1g")
53+ fabric2 = factory.make_Fabric(class_type="10g")
54+ vlan1 = factory.make_VLAN(vid=1, fabric=fabric1)
55+ vlan2 = factory.make_VLAN(vid=2, fabric=fabric2)
56+ node1 = factory.make_Node_with_Interface_on_Subnet(
57+ fabric=fabric1, vlan=vlan1)
58+ node2 = factory.make_Node_with_Interface_on_Subnet(
59+ fabric=fabric2, vlan=vlan2)
60+
61+ form = AcquireNodeForm({
62+ u'interfaces':
63+ u'none:fabric_class=1g,vid=2'})
64+ self.assertTrue(form.is_valid(), dict(form.errors))
65+ filtered_nodes, _, _ = form.filter_nodes(Node.nodes)
66+ self.assertItemsEqual([], filtered_nodes)
67+
68+ form = AcquireNodeForm({
69+ u'interfaces':
70+ u'any:fabric_class=10g,fabric_class=1g,vid=1,vid=2'})
71+ self.assertTrue(form.is_valid(), dict(form.errors))
72+ filtered_nodes, _, _ = form.filter_nodes(Node.nodes)
73+ self.assertItemsEqual([node1, node2], filtered_nodes)
74+
75 def test_combined_constraints(self):
76 tag_big = factory.make_Tag(name='big')
77 arch = '%s/generic' % factory.make_name('arch')
78
79=== modified file 'src/maasserver/utils/orm.py'
80--- src/maasserver/utils/orm.py 2015-11-07 00:56:58 +0000
81+++ src/maasserver/utils/orm.py 2015-11-21 00:08:55 +0000
82@@ -801,6 +801,12 @@
83 inferred that the user would like a set of queries joined with
84 logical AND operators.
85
86+ If the list of specifiers is given as a dict, it is inferred that each
87+ key is a specifier type, and each value is a list of specifier values.
88+ The specifier values inside each list will be joined with logical OR
89+ operators. The lists for each key will be joined with logical AND
90+ operators.
91+
92 For example, 'name:eth0,hostname:tasty-buscuits' might match interface
93 eth0 on node 'tasty-biscuits'; that is, both constraints are required.
94 """
95@@ -810,6 +816,8 @@
96 return [
97 '&' + specifier.strip() for specifier in specifiers.split(',')
98 ]
99+ elif isinstance(specifiers, dict):
100+ return specifiers
101 else:
102 return list(flatten(specifiers))
103
104@@ -894,13 +902,28 @@
105 if specifier_types is None:
106 raise NotImplementedError("Subclass must specify specifier_types.")
107 current_q = Q()
108- for item in specifiers:
109- item, op = parse_item_operation(item)
110- item, specifier_type = parse_item_specifier_type(
111- item, spec_types=specifier_types, separator=separator)
112- query = self.get_filter_function(
113- specifier_type, specifier_types, item, separator=separator)
114- current_q = query(current_q, op, item)
115+ if isinstance(specifiers, dict):
116+ # If we got a dictionary, treat it as one of the entries in a
117+ # LabeledConstraintMap. That is, each key is a specifier, and
118+ # each value is a list of values (which must be OR'd together).
119+ for key in specifiers.iterkeys():
120+ assert isinstance(specifiers[key], list)
121+ constraints = [
122+ key + separator + value
123+ for value in specifiers[key]
124+ ]
125+ # Leave off specifier_types here because this recursion
126+ # will go back to the subclass to get the types filled in.
127+ current_q &= self.get_specifiers_q(
128+ constraints, separator=separator)
129+ else:
130+ for item in specifiers:
131+ item, op = parse_item_operation(item)
132+ item, specifier_type = parse_item_specifier_type(
133+ item, spec_types=specifier_types, separator=separator)
134+ query = self.get_filter_function(
135+ specifier_type, specifier_types, item, separator=separator)
136+ current_q = query(current_q, op, item)
137 if len(kwargs) > 0:
138 current_q &= Q(**kwargs)
139 return current_q

Subscribers

People subscribed via source and target branches