Merge lp:~mpontillo/maas/networking-constraints-refactor-part3 into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 4457
Proposed branch: lp:~mpontillo/maas/networking-constraints-refactor-part3
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 119 lines (+59/-1)
4 files modified
src/maasserver/models/interface.py (+1/-0)
src/maasserver/models/tests/test_subnet.py (+46/-0)
src/maasserver/node_constraint_filter_forms.py (+1/-1)
src/maasserver/utils/orm.py (+11/-0)
To merge this branch: bzr merge lp:~mpontillo/maas/networking-constraints-refactor-part3
Reviewer Review Type Date Requested Status
Ricardo Bánffy (community) Approve
Review via email: mp+276479@code.launchpad.net

Commit message

Network interfaces constraint refactoring, part 3.

 * Implement negative constraints (via a generic way to specify inverse versions of specifier-based queries.)

To post a comment you must log in.
Revision history for this message
Ricardo Bánffy (rbanffy) wrote :

Looks good. Have I mentioned how much I love docstrings?

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Thanks for the review. I'll think about adding more docstrings before I land part4.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/models/interface.py'
2--- src/maasserver/models/interface.py 2015-11-02 20:24:09 +0000
3+++ src/maasserver/models/interface.py 2015-11-03 01:11:53 +0000
4@@ -119,6 +119,7 @@
5
6
7 class InterfaceQueriesMixin(MAASQueriesMixin):
8+
9 def get_specifiers_q(self, specifiers, separator=':'):
10 """Returns a Q object for objects matching the given specifiers.
11
12
13=== modified file 'src/maasserver/models/tests/test_subnet.py'
14--- src/maasserver/models/tests/test_subnet.py 2015-11-01 05:32:17 +0000
15+++ src/maasserver/models/tests/test_subnet.py 2015-11-03 01:11:53 +0000
16@@ -273,6 +273,52 @@
17 "interface:id:%s" % iface2.id]),
18 [subnet1, subnet2])
19
20+ def test__not_operators(self):
21+ node1 = factory.make_Node_with_Interface_on_Subnet()
22+ node2 = factory.make_Node_with_Interface_on_Subnet()
23+ iface1 = node1.get_boot_interface()
24+ iface2 = node2.get_boot_interface()
25+ subnet1 = iface1.ip_addresses.first().subnet
26+ self.assertItemsEqual(
27+ Subnet.objects.filter_by_specifiers(
28+ ["interface:id:%s" % iface1.id,
29+ "!interface:id:%s" % iface2.id]),
30+ [subnet1])
31+ self.assertItemsEqual(
32+ Subnet.objects.filter_by_specifiers(
33+ ["interface:id:%s" % iface1.id,
34+ "not_interface:id:%s" % iface2.id]),
35+ [subnet1])
36+
37+ def test__not_operators_order_independent(self):
38+ node1 = factory.make_Node_with_Interface_on_Subnet()
39+ node2 = factory.make_Node_with_Interface_on_Subnet()
40+ iface1 = node1.get_boot_interface()
41+ iface2 = node2.get_boot_interface()
42+ subnet2 = iface2.ip_addresses.first().subnet
43+ self.assertItemsEqual(
44+ Subnet.objects.filter_by_specifiers(
45+ ["!interface:id:%s" % iface1.id,
46+ "interface:id:%s" % iface2.id]),
47+ [subnet2])
48+ self.assertItemsEqual(
49+ Subnet.objects.filter_by_specifiers(
50+ ["not_interface:id:%s" % iface1.id,
51+ "interface:id:%s" % iface2.id]),
52+ [subnet2])
53+
54+ def test__and_operator(self):
55+ node1 = factory.make_Node_with_Interface_on_Subnet()
56+ node2 = factory.make_Node_with_Interface_on_Subnet()
57+ iface1 = node1.get_boot_interface()
58+ iface2 = node2.get_boot_interface()
59+ # Try to filter by two mutually exclusive conditions.
60+ self.assertItemsEqual(
61+ Subnet.objects.filter_by_specifiers(
62+ ["interface:id:%s" % iface1.id,
63+ "&interface:id:%s" % iface2.id]),
64+ [])
65+
66 def test__craziness_works(self):
67 # This test validates that filters can be "chained" to each other
68 # in an arbitrary way.
69
70=== modified file 'src/maasserver/node_constraint_filter_forms.py'
71--- src/maasserver/node_constraint_filter_forms.py 2015-11-02 22:17:02 +0000
72+++ src/maasserver/node_constraint_filter_forms.py 2015-11-03 01:11:53 +0000
73@@ -380,7 +380,7 @@
74
75 def nodes_by_interface(interfaces_label_map):
76 """Determines the set of nodes that match the specified
77- LabeledConstraintMap (which must be a map of storage constraints.)
78+ LabeledConstraintMap (which must be a map of interface constraints.)
79
80 :param interfaces_label_map: LabeledConstraintMap
81 :return: set
82
83=== modified file 'src/maasserver/utils/orm.py'
84--- src/maasserver/utils/orm.py 2015-11-02 22:58:39 +0000
85+++ src/maasserver/utils/orm.py 2015-11-03 01:11:53 +0000
86@@ -684,6 +684,9 @@
87
88 If the first character in the specifier is '&', the operator will be AND.
89
90+ If the first character in the specifier is '!', or the specifier starts
91+ with "not_", the operator will be AND(existing_query, ~(new_query)).
92+
93 If unspecified, the default operator is OR.
94
95 :param specifier: a string containing the specifier.
96@@ -693,8 +696,10 @@
97
98 from operator import (
99 and_ as AND,
100+ inv as INV,
101 or_ as OR,
102 )
103+ AND_NOT = lambda current, next: AND(current, INV(next))
104
105 if specifier.startswith('|'):
106 op = OR
107@@ -702,6 +707,12 @@
108 elif specifier.startswith('&'):
109 op = AND
110 specifier = specifier[1:]
111+ elif specifier.startswith('not_'):
112+ op = AND_NOT
113+ specifier = specifier[4:]
114+ elif specifier.startswith('!'):
115+ op = AND_NOT
116+ specifier = specifier[1:]
117 else:
118 # Default to OR.
119 op = OR