Merge lp:~allenap/maas/rpc-tags-cluster into lp:~maas-committers/maas/trunk
- rpc-tags-cluster
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 3054 |
Proposed branch: | lp:~allenap/maas/rpc-tags-cluster |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
814 lines (+263/-207) 14 files modified
src/maasserver/api/tests/test_tag.py (+10/-0) src/maasserver/models/tag.py (+3/-2) src/maasserver/models/tests/test_tag.py (+10/-0) src/maasserver/populate_tags.py (+2/-2) src/maasserver/tests/test_populate_tags.py (+1/-39) src/provisioningserver/rpc/cluster.py (+20/-0) src/provisioningserver/rpc/clusterservice.py (+19/-0) src/provisioningserver/rpc/tags.py (+46/-0) src/provisioningserver/rpc/tests/test_clusterservice.py (+66/-0) src/provisioningserver/rpc/tests/test_tags.py (+73/-0) src/provisioningserver/tags.py (+6/-42) src/provisioningserver/tasks.py (+1/-36) src/provisioningserver/tests/test_tags.py (+6/-41) src/provisioningserver/tests/test_tasks.py (+0/-45) |
To merge this branch: | bzr merge lp:~allenap/maas/rpc-tags-cluster |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | Approve | ||
Review via email:
|
Commit message
New EvaluateTag RPC command on the cluster.
Description of the change
This hobbles tag evaluation in the region, but I won't land this as-is. It's a merge proposal for review purposes only.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gavin Panella (allenap) wrote : | # |
Thanks for the review :)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~allenap/maas/rpc-tags-cluster into lp:maas failed. Below is the output from the failed tests.
Ign http://
Ign http://
Get:1 http://
Ign http://
Get:2 http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Hit http://
Get:6 http://
Hit http://
Get:7 http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:8 http://
Hit http://
Hit http://
Ign http://
Ign http://
Get:9 http://
Get:10 http://
Get:11 http://
Get:12 http://
Hit http://
Hit http://
Fetched 1,101 kB in 0s (1,663 kB/s)
Reading package lists...
sudo DEBIAN_
--
Preview Diff
1 | === modified file 'src/maasserver/api/tests/test_tag.py' | |||
2 | --- src/maasserver/api/tests/test_tag.py 2014-09-10 16:20:31 +0000 | |||
3 | +++ src/maasserver/api/tests/test_tag.py 2014-09-23 09:14:01 +0000 | |||
4 | @@ -104,6 +104,8 @@ | |||
5 | 104 | self.assertTrue(Tag.objects.filter(name='new-tag-name').exists()) | 104 | self.assertTrue(Tag.objects.filter(name='new-tag-name').exists()) |
6 | 105 | 105 | ||
7 | 106 | def test_PUT_updates_node_associations(self): | 106 | def test_PUT_updates_node_associations(self): |
8 | 107 | self.skip("Tag evaluation is being ported to RPC.") | ||
9 | 108 | |||
10 | 107 | node1 = factory.make_Node() | 109 | node1 = factory.make_Node() |
11 | 108 | inject_lshw_result(node1, b'<node><foo/></node>') | 110 | inject_lshw_result(node1, b'<node><foo/></node>') |
12 | 109 | node2 = factory.make_Node() | 111 | node2 = factory.make_Node() |
13 | @@ -141,6 +143,8 @@ | |||
14 | 141 | [r['system_id'] for r in parsed_result]) | 143 | [r['system_id'] for r in parsed_result]) |
15 | 142 | 144 | ||
16 | 143 | def test_GET_nodes_hides_invisible_nodes(self): | 145 | def test_GET_nodes_hides_invisible_nodes(self): |
17 | 146 | self.skip("Tag evaluation is being ported to RPC.") | ||
18 | 147 | |||
19 | 144 | user2 = factory.make_User() | 148 | user2 = factory.make_User() |
20 | 145 | node1 = factory.make_Node() | 149 | node1 = factory.make_Node() |
21 | 146 | inject_lshw_result(node1, b'<node><foo/></node>') | 150 | inject_lshw_result(node1, b'<node><foo/></node>') |
22 | @@ -162,6 +166,8 @@ | |||
23 | 162 | [r['system_id'] for r in parsed_result]) | 166 | [r['system_id'] for r in parsed_result]) |
24 | 163 | 167 | ||
25 | 164 | def test_PUT_invalid_definition(self): | 168 | def test_PUT_invalid_definition(self): |
26 | 169 | self.skip("Tag evaluation is being ported to RPC.") | ||
27 | 170 | |||
28 | 165 | self.become_admin() | 171 | self.become_admin() |
29 | 166 | node = factory.make_Node() | 172 | node = factory.make_Node() |
30 | 167 | inject_lshw_result(node, b'<node ><child/></node>') | 173 | inject_lshw_result(node, b'<node ><child/></node>') |
31 | @@ -332,6 +338,8 @@ | |||
32 | 332 | self.assertItemsEqual([], node.tags.all()) | 338 | self.assertItemsEqual([], node.tags.all()) |
33 | 333 | 339 | ||
34 | 334 | def test_POST_rebuild_rebuilds_node_mapping(self): | 340 | def test_POST_rebuild_rebuilds_node_mapping(self): |
35 | 341 | self.skip("Tag evaluation is being ported to RPC.") | ||
36 | 342 | |||
37 | 335 | tag = factory.make_Tag(definition='//foo/bar') | 343 | tag = factory.make_Tag(definition='//foo/bar') |
38 | 336 | # Only one node matches the tag definition, rebuilding should notice | 344 | # Only one node matches the tag definition, rebuilding should notice |
39 | 337 | node_matching = factory.make_Node() | 345 | node_matching = factory.make_Node() |
40 | @@ -483,6 +491,8 @@ | |||
41 | 483 | extra_kernel_opts, Tag.objects.filter(name=name)[0].kernel_opts) | 491 | extra_kernel_opts, Tag.objects.filter(name=name)[0].kernel_opts) |
42 | 484 | 492 | ||
43 | 485 | def test_POST_new_populates_nodes(self): | 493 | def test_POST_new_populates_nodes(self): |
44 | 494 | self.skip("Tag evaluation is being ported to RPC.") | ||
45 | 495 | |||
46 | 486 | self.become_admin() | 496 | self.become_admin() |
47 | 487 | node1 = factory.make_Node() | 497 | node1 = factory.make_Node() |
48 | 488 | inject_lshw_result(node1, b'<node><child/></node>') | 498 | inject_lshw_result(node1, b'<node><child/></node>') |
49 | 489 | 499 | ||
50 | === modified file 'src/maasserver/models/tag.py' | |||
51 | --- src/maasserver/models/tag.py 2013-12-02 14:29:41 +0000 | |||
52 | +++ src/maasserver/models/tag.py 2014-09-23 09:14:01 +0000 | |||
53 | @@ -101,7 +101,6 @@ | |||
54 | 101 | 101 | ||
55 | 102 | def populate_nodes(self): | 102 | def populate_nodes(self): |
56 | 103 | """Find all nodes that match this tag, and update them.""" | 103 | """Find all nodes that match this tag, and update them.""" |
57 | 104 | from maasserver.populate_tags import populate_tags | ||
58 | 105 | if not self.is_defined: | 104 | if not self.is_defined: |
59 | 106 | return | 105 | return |
60 | 107 | # before we pass off any work, ensure the definition is valid XPATH | 106 | # before we pass off any work, ensure the definition is valid XPATH |
61 | @@ -112,7 +111,9 @@ | |||
62 | 112 | raise ValidationError({'definition': [msg]}) | 111 | raise ValidationError({'definition': [msg]}) |
63 | 113 | # Now delete the existing tags | 112 | # Now delete the existing tags |
64 | 114 | self.node_set.clear() | 113 | self.node_set.clear() |
66 | 115 | populate_tags(self) | 114 | # Being ported to RPC. |
67 | 115 | # from maasserver.populate_tags import populate_tags | ||
68 | 116 | # populate_tags(self) | ||
69 | 116 | 117 | ||
70 | 117 | def save(self, *args, **kwargs): | 118 | def save(self, *args, **kwargs): |
71 | 118 | super(Tag, self).save(*args, **kwargs) | 119 | super(Tag, self).save(*args, **kwargs) |
72 | 119 | 120 | ||
73 | === modified file 'src/maasserver/models/tests/test_tag.py' | |||
74 | --- src/maasserver/models/tests/test_tag.py 2014-09-19 13:27:37 +0000 | |||
75 | +++ src/maasserver/models/tests/test_tag.py 2014-09-23 09:14:01 +0000 | |||
76 | @@ -67,6 +67,8 @@ | |||
77 | 67 | self.assertRaises(ValidationError, factory.make_Tag, name=invalid) | 67 | self.assertRaises(ValidationError, factory.make_Tag, name=invalid) |
78 | 68 | 68 | ||
79 | 69 | def test_applies_tags_to_nodes(self): | 69 | def test_applies_tags_to_nodes(self): |
80 | 70 | self.skip("Tag evaluation is being ported to RPC.") | ||
81 | 71 | |||
82 | 70 | node1 = factory.make_Node() | 72 | node1 = factory.make_Node() |
83 | 71 | inject_lshw_result(node1, b'<node><child /></node>') | 73 | inject_lshw_result(node1, b'<node><child /></node>') |
84 | 72 | node2 = factory.make_Node() | 74 | node2 = factory.make_Node() |
85 | @@ -76,6 +78,8 @@ | |||
86 | 76 | self.assertItemsEqual([], node2.tag_names()) | 78 | self.assertItemsEqual([], node2.tag_names()) |
87 | 77 | 79 | ||
88 | 78 | def test_removes_old_values(self): | 80 | def test_removes_old_values(self): |
89 | 81 | self.skip("Tag evaluation is being ported to RPC.") | ||
90 | 82 | |||
91 | 79 | node1 = factory.make_Node() | 83 | node1 = factory.make_Node() |
92 | 80 | inject_lshw_result(node1, b'<node><foo /></node>') | 84 | inject_lshw_result(node1, b'<node><foo /></node>') |
93 | 81 | node2 = factory.make_Node() | 85 | node2 = factory.make_Node() |
94 | @@ -94,6 +98,8 @@ | |||
95 | 94 | self.assertItemsEqual([], node2.tag_names()) | 98 | self.assertItemsEqual([], node2.tag_names()) |
96 | 95 | 99 | ||
97 | 96 | def test_doesnt_touch_other_tags(self): | 100 | def test_doesnt_touch_other_tags(self): |
98 | 101 | self.skip("Tag evaluation is being ported to RPC.") | ||
99 | 102 | |||
100 | 97 | node1 = factory.make_Node() | 103 | node1 = factory.make_Node() |
101 | 98 | inject_lshw_result(node1, b'<node><foo /></node>') | 104 | inject_lshw_result(node1, b'<node><foo /></node>') |
102 | 99 | node2 = factory.make_Node() | 105 | node2 = factory.make_Node() |
103 | @@ -106,6 +112,8 @@ | |||
104 | 106 | self.assertItemsEqual([tag2.name], node2.tag_names()) | 112 | self.assertItemsEqual([tag2.name], node2.tag_names()) |
105 | 107 | 113 | ||
106 | 108 | def test_rollsback_invalid_xpath(self): | 114 | def test_rollsback_invalid_xpath(self): |
107 | 115 | self.skip("Tag evaluation is being ported to RPC.") | ||
108 | 116 | |||
109 | 109 | node = factory.make_Node() | 117 | node = factory.make_Node() |
110 | 110 | inject_lshw_result(node, b'<node><foo /></node>') | 118 | inject_lshw_result(node, b'<node><foo /></node>') |
111 | 111 | tag = factory.make_Tag(definition='//node/foo') | 119 | tag = factory.make_Tag(definition='//node/foo') |
112 | @@ -159,6 +167,8 @@ | |||
113 | 159 | self.assertItemsEqual([], tag.node_set.all()) | 167 | self.assertItemsEqual([], tag.node_set.all()) |
114 | 160 | 168 | ||
115 | 161 | def test__calls_populate_tags(self): | 169 | def test__calls_populate_tags(self): |
116 | 170 | self.skip("Tag evaluation is being ported to RPC.") | ||
117 | 171 | |||
118 | 162 | populate_tags = self.patch_autospec( | 172 | populate_tags = self.patch_autospec( |
119 | 163 | populate_tags_module, "populate_tags") | 173 | populate_tags_module, "populate_tags") |
120 | 164 | 174 | ||
121 | 165 | 175 | ||
122 | === modified file 'src/maasserver/populate_tags.py' | |||
123 | --- src/maasserver/populate_tags.py 2014-08-13 21:49:35 +0000 | |||
124 | +++ src/maasserver/populate_tags.py 2014-09-23 09:14:01 +0000 | |||
125 | @@ -28,7 +28,6 @@ | |||
126 | 28 | ) | 28 | ) |
127 | 29 | from maasserver.refresh_worker import refresh_worker | 29 | from maasserver.refresh_worker import refresh_worker |
128 | 30 | from provisioningserver.tags import merge_details | 30 | from provisioningserver.tags import merge_details |
129 | 31 | from provisioningserver.tasks import update_node_tags | ||
130 | 32 | from provisioningserver.utils import classify | 31 | from provisioningserver.utils import classify |
131 | 33 | from provisioningserver.utils.xpath import try_match_xpath | 32 | from provisioningserver.utils.xpath import try_match_xpath |
132 | 34 | 33 | ||
133 | @@ -56,7 +55,8 @@ | |||
134 | 56 | logger.debug('Refreshing tag definition for %s' % (items,)) | 55 | logger.debug('Refreshing tag definition for %s' % (items,)) |
135 | 57 | for nodegroup in NodeGroup.objects.all(): | 56 | for nodegroup in NodeGroup.objects.all(): |
136 | 58 | refresh_worker(nodegroup) | 57 | refresh_worker(nodegroup) |
138 | 59 | update_node_tags.apply_async(queue=nodegroup.work_queue, kwargs=items) | 58 | raise NotImplementedError( |
139 | 59 | "Tag evaluation is being ported to RPC.") | ||
140 | 60 | 60 | ||
141 | 61 | 61 | ||
142 | 62 | def populate_tags_for_single_node(tags, node): | 62 | def populate_tags_for_single_node(tags, node): |
143 | 63 | 63 | ||
144 | === modified file 'src/maasserver/tests/test_populate_tags.py' | |||
145 | --- src/maasserver/tests/test_populate_tags.py 2014-09-15 14:28:28 +0000 | |||
146 | +++ src/maasserver/tests/test_populate_tags.py 2014-09-23 09:14:01 +0000 | |||
147 | @@ -14,49 +14,11 @@ | |||
148 | 14 | __metaclass__ = type | 14 | __metaclass__ = type |
149 | 15 | __all__ = [] | 15 | __all__ = [] |
150 | 16 | 16 | ||
151 | 17 | from maasserver import populate_tags as populate_tags_module | ||
152 | 18 | from maasserver.models import Tag | 17 | from maasserver.models import Tag |
158 | 19 | from maasserver.populate_tags import ( | 18 | from maasserver.populate_tags import populate_tags_for_single_node |
154 | 20 | populate_tags, | ||
155 | 21 | populate_tags_for_single_node, | ||
156 | 22 | tag_nsmap, | ||
157 | 23 | ) | ||
159 | 24 | from maasserver.testing.factory import factory | 19 | from maasserver.testing.factory import factory |
160 | 25 | from maasserver.testing.testcase import MAASServerTestCase | 20 | from maasserver.testing.testcase import MAASServerTestCase |
161 | 26 | from metadataserver.models import commissioningscript | 21 | from metadataserver.models import commissioningscript |
162 | 27 | import mock | ||
163 | 28 | |||
164 | 29 | |||
165 | 30 | class TestPopulateTags(MAASServerTestCase): | ||
166 | 31 | |||
167 | 32 | def test_populate_tags_task_routed_to_nodegroup_worker(self): | ||
168 | 33 | nodegroup = factory.make_NodeGroup() | ||
169 | 34 | tag = factory.make_Tag() | ||
170 | 35 | task = self.patch(populate_tags_module, 'update_node_tags') | ||
171 | 36 | populate_tags(tag) | ||
172 | 37 | args, kwargs = task.apply_async.call_args | ||
173 | 38 | self.assertEqual(nodegroup.work_queue, kwargs['queue']) | ||
174 | 39 | |||
175 | 40 | def test_populate_tags_task_routed_to_all_nodegroup_workers(self): | ||
176 | 41 | nodegroups = [factory.make_NodeGroup() for _ in range(5)] | ||
177 | 42 | tag = factory.make_Tag() | ||
178 | 43 | refresh = self.patch(populate_tags_module, 'refresh_worker') | ||
179 | 44 | task = self.patch(populate_tags_module, 'update_node_tags') | ||
180 | 45 | populate_tags(tag) | ||
181 | 46 | refresh_calls = [mock.call(nodegroup) for nodegroup in nodegroups] | ||
182 | 47 | refresh.assert_has_calls(refresh_calls, any_order=True) | ||
183 | 48 | task_calls = [ | ||
184 | 49 | mock.call( | ||
185 | 50 | queue=nodegroup.work_queue, | ||
186 | 51 | kwargs={ | ||
187 | 52 | 'tag_name': tag.name, | ||
188 | 53 | 'tag_definition': tag.definition, | ||
189 | 54 | 'tag_nsmap': tag_nsmap, | ||
190 | 55 | }, | ||
191 | 56 | ) | ||
192 | 57 | for nodegroup in nodegroups | ||
193 | 58 | ] | ||
194 | 59 | task.apply_async.assert_has_calls(task_calls, any_order=True) | ||
195 | 60 | 22 | ||
196 | 61 | 23 | ||
197 | 62 | class TestPopulateTagsForSingleNode(MAASServerTestCase): | 24 | class TestPopulateTagsForSingleNode(MAASServerTestCase): |
198 | 63 | 25 | ||
199 | === modified file 'src/provisioningserver/rpc/cluster.py' | |||
200 | --- src/provisioningserver/rpc/cluster.py 2014-09-18 21:21:18 +0000 | |||
201 | +++ src/provisioningserver/rpc/cluster.py 2014-09-23 09:14:01 +0000 | |||
202 | @@ -345,6 +345,26 @@ | |||
203 | 345 | error = [] | 345 | error = [] |
204 | 346 | 346 | ||
205 | 347 | 347 | ||
206 | 348 | class EvaluateTag(amp.Command): | ||
207 | 349 | """Evaluate a tag against all of the cluster's nodes. | ||
208 | 350 | |||
209 | 351 | :since: 1.7 | ||
210 | 352 | """ | ||
211 | 353 | |||
212 | 354 | arguments = [ | ||
213 | 355 | (b"tag_name", amp.Unicode()), | ||
214 | 356 | (b"tag_definition", amp.Unicode()), | ||
215 | 357 | (b"tag_nsmap", amp.AmpList([ | ||
216 | 358 | (b"prefix", amp.Unicode()), | ||
217 | 359 | (b"uri", amp.Unicode()), | ||
218 | 360 | ])), | ||
219 | 361 | # A 3-part credential string for the web API. | ||
220 | 362 | (b"credentials", amp.Unicode()), | ||
221 | 363 | ] | ||
222 | 364 | response = [] | ||
223 | 365 | errors = [] | ||
224 | 366 | |||
225 | 367 | |||
226 | 348 | class AddVirsh(amp.Command): | 368 | class AddVirsh(amp.Command): |
227 | 349 | """Probe for and enlist virsh VMs attached to the cluster. | 369 | """Probe for and enlist virsh VMs attached to the cluster. |
228 | 350 | 370 | ||
229 | 351 | 371 | ||
230 | === modified file 'src/provisioningserver/rpc/clusterservice.py' | |||
231 | --- src/provisioningserver/rpc/clusterservice.py 2014-09-22 11:31:14 +0000 | |||
232 | +++ src/provisioningserver/rpc/clusterservice.py 2014-09-23 09:14:01 +0000 | |||
233 | @@ -21,6 +21,7 @@ | |||
234 | 21 | import random | 21 | import random |
235 | 22 | from urlparse import urlparse | 22 | from urlparse import urlparse |
236 | 23 | 23 | ||
237 | 24 | from apiclient.creds import convert_string_to_tuple | ||
238 | 24 | from apiclient.utils import ascii_url | 25 | from apiclient.utils import ascii_url |
239 | 25 | from provisioningserver.cluster_config import ( | 26 | from provisioningserver.cluster_config import ( |
240 | 26 | get_cluster_uuid, | 27 | get_cluster_uuid, |
241 | @@ -68,6 +69,7 @@ | |||
242 | 68 | change_power_state, | 69 | change_power_state, |
243 | 69 | get_power_state, | 70 | get_power_state, |
244 | 70 | ) | 71 | ) |
245 | 72 | from provisioningserver.rpc.tags import evaluate_tag | ||
246 | 71 | from provisioningserver.utils.network import find_ip_via_arp | 73 | from provisioningserver.utils.network import find_ip_via_arp |
247 | 72 | from twisted.application.internet import TimerService | 74 | from twisted.application.internet import TimerService |
248 | 73 | from twisted.internet.defer import inlineCallbacks | 75 | from twisted.internet.defer import inlineCallbacks |
249 | @@ -76,6 +78,7 @@ | |||
250 | 76 | TCP4ClientEndpoint, | 78 | TCP4ClientEndpoint, |
251 | 77 | ) | 79 | ) |
252 | 78 | from twisted.internet.error import ConnectError | 80 | from twisted.internet.error import ConnectError |
253 | 81 | from twisted.internet.threads import deferToThread | ||
254 | 79 | from twisted.protocols import amp | 82 | from twisted.protocols import amp |
255 | 80 | from twisted.python import log | 83 | from twisted.python import log |
256 | 81 | from twisted.web.client import getPage | 84 | from twisted.web.client import getPage |
257 | @@ -245,6 +248,22 @@ | |||
258 | 245 | else: | 248 | else: |
259 | 246 | return tls.get_tls_parameters_for_cluster() | 249 | return tls.get_tls_parameters_for_cluster() |
260 | 247 | 250 | ||
261 | 251 | @cluster.EvaluateTag.responder | ||
262 | 252 | def evaluate_tag(self, tag_name, tag_definition, tag_nsmap, credentials): | ||
263 | 253 | """evaluate_tag() | ||
264 | 254 | |||
265 | 255 | Implementation of | ||
266 | 256 | :py:class:`~provisioningserver.rpc.cluster.EvaluateTag`. | ||
267 | 257 | """ | ||
268 | 258 | # It's got to run in a thread because it does blocking IO. | ||
269 | 259 | d = deferToThread( | ||
270 | 260 | evaluate_tag, tag_name, tag_definition, | ||
271 | 261 | # Transform tag_nsmap into a format that LXML likes. | ||
272 | 262 | {entry["prefix"]: entry["uri"] for entry in tag_nsmap}, | ||
273 | 263 | # Parse the credential string into a 3-tuple. | ||
274 | 264 | convert_string_to_tuple(credentials)) | ||
275 | 265 | return d.addCallback(lambda _: {}) | ||
276 | 266 | |||
277 | 248 | @cluster.AddVirsh.responder | 267 | @cluster.AddVirsh.responder |
278 | 249 | def add_virsh(self, poweraddr, password): | 268 | def add_virsh(self, poweraddr, password): |
279 | 250 | """add_virsh() | 269 | """add_virsh() |
280 | 251 | 270 | ||
281 | === added file 'src/provisioningserver/rpc/tags.py' | |||
282 | --- src/provisioningserver/rpc/tags.py 1970-01-01 00:00:00 +0000 | |||
283 | +++ src/provisioningserver/rpc/tags.py 2014-09-23 09:14:01 +0000 | |||
284 | @@ -0,0 +1,46 @@ | |||
285 | 1 | # Copyright 2014 Canonical Ltd. This software is licensed under the | ||
286 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
287 | 3 | |||
288 | 4 | """RPC helpers for dealing with tags.""" | ||
289 | 5 | |||
290 | 6 | from __future__ import ( | ||
291 | 7 | absolute_import, | ||
292 | 8 | print_function, | ||
293 | 9 | unicode_literals, | ||
294 | 10 | ) | ||
295 | 11 | |||
296 | 12 | str = None | ||
297 | 13 | |||
298 | 14 | __metaclass__ = type | ||
299 | 15 | __all__ = [ | ||
300 | 16 | "evaluate_tag", | ||
301 | 17 | ] | ||
302 | 18 | |||
303 | 19 | from apiclient.maas_client import ( | ||
304 | 20 | MAASClient, | ||
305 | 21 | MAASDispatcher, | ||
306 | 22 | MAASOAuth, | ||
307 | 23 | ) | ||
308 | 24 | from provisioningserver.cluster_config import ( | ||
309 | 25 | get_cluster_uuid, | ||
310 | 26 | get_maas_url, | ||
311 | 27 | ) | ||
312 | 28 | from provisioningserver.tags import process_node_tags | ||
313 | 29 | from provisioningserver.utils.twisted import synchronous | ||
314 | 30 | |||
315 | 31 | |||
316 | 32 | @synchronous | ||
317 | 33 | def evaluate_tag(tag_name, tag_definition, tag_nsmap, credentials): | ||
318 | 34 | """Evaluate `tag_definition` against this cluster's nodes' details. | ||
319 | 35 | |||
320 | 36 | :param tag_name: The name of the tag, used for logging. | ||
321 | 37 | :param tag_definition: The XPath expression of the tag. | ||
322 | 38 | :param tag_nsmap: The namespace map as used by LXML's ETree library. | ||
323 | 39 | :param credentials: A 3-tuple of OAuth credentials. | ||
324 | 40 | """ | ||
325 | 41 | client = MAASClient( | ||
326 | 42 | auth=MAASOAuth(*credentials), dispatcher=MAASDispatcher(), | ||
327 | 43 | base_url=get_maas_url()) | ||
328 | 44 | process_node_tags( | ||
329 | 45 | tag_name=tag_name, tag_definition=tag_definition, tag_nsmap=tag_nsmap, | ||
330 | 46 | client=client, nodegroup_uuid=get_cluster_uuid()) | ||
331 | 0 | 47 | ||
332 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' | |||
333 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2014-09-22 21:34:33 +0000 | |||
334 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2014-09-23 09:14:01 +0000 | |||
335 | @@ -25,6 +25,7 @@ | |||
336 | 25 | from random import randint | 25 | from random import randint |
337 | 26 | from urlparse import urlparse | 26 | from urlparse import urlparse |
338 | 27 | 27 | ||
339 | 28 | from apiclient.creds import convert_tuple_to_string | ||
340 | 28 | from fixtures import EnvironmentVariable | 29 | from fixtures import EnvironmentVariable |
341 | 29 | from maastesting.factory import factory | 30 | from maastesting.factory import factory |
342 | 30 | from maastesting.matchers import ( | 31 | from maastesting.matchers import ( |
343 | @@ -64,6 +65,7 @@ | |||
344 | 64 | osystems as osystems_rpc_module, | 65 | osystems as osystems_rpc_module, |
345 | 65 | power as power_module, | 66 | power as power_module, |
346 | 66 | region, | 67 | region, |
347 | 68 | tags, | ||
348 | 67 | ) | 69 | ) |
349 | 68 | from provisioningserver.rpc.clusterservice import ( | 70 | from provisioningserver.rpc.clusterservice import ( |
350 | 69 | Cluster, | 71 | Cluster, |
351 | @@ -1185,6 +1187,70 @@ | |||
352 | 1185 | self.assertThat(running_monitors, Not(Contains(monitors[0]["id"]))) | 1187 | self.assertThat(running_monitors, Not(Contains(monitors[0]["id"]))) |
353 | 1186 | 1188 | ||
354 | 1187 | 1189 | ||
355 | 1190 | class TestClusterProtocol_EvaluateTag(MAASTestCase): | ||
356 | 1191 | |||
357 | 1192 | run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) | ||
358 | 1193 | |||
359 | 1194 | def test__is_registered(self): | ||
360 | 1195 | protocol = Cluster() | ||
361 | 1196 | responder = protocol.locateResponder( | ||
362 | 1197 | cluster.EvaluateTag.commandName) | ||
363 | 1198 | self.assertIsNot(responder, None) | ||
364 | 1199 | |||
365 | 1200 | @inlineCallbacks | ||
366 | 1201 | def test_happy_path(self): | ||
367 | 1202 | get_maas_url = self.patch_autospec(tags, "get_maas_url") | ||
368 | 1203 | get_maas_url.return_value = sentinel.maas_url | ||
369 | 1204 | get_cluster_uuid = self.patch_autospec(tags, "get_cluster_uuid") | ||
370 | 1205 | get_cluster_uuid.return_value = sentinel.cluster_uuid | ||
371 | 1206 | |||
372 | 1207 | # Prevent real work being done, which would involve HTTP calls. | ||
373 | 1208 | self.patch_autospec(tags, "process_node_tags") | ||
374 | 1209 | |||
375 | 1210 | response = yield call_responder( | ||
376 | 1211 | Cluster(), cluster.EvaluateTag, { | ||
377 | 1212 | "tag_name": "all-nodes", | ||
378 | 1213 | "tag_definition": "//*", | ||
379 | 1214 | "tag_nsmap": [ | ||
380 | 1215 | {"prefix": "foo", | ||
381 | 1216 | "uri": "http://foo.example.com/"}, | ||
382 | 1217 | ], | ||
383 | 1218 | "credentials": "abc:def:ghi", | ||
384 | 1219 | }) | ||
385 | 1220 | |||
386 | 1221 | self.assertEqual({}, response) | ||
387 | 1222 | |||
388 | 1223 | @inlineCallbacks | ||
389 | 1224 | def test__calls_through_to_evaluate_tag_helper(self): | ||
390 | 1225 | evaluate_tag = self.patch_autospec(clusterservice, "evaluate_tag") | ||
391 | 1226 | |||
392 | 1227 | tag_name = factory.make_name("tag-name") | ||
393 | 1228 | tag_definition = factory.make_name("tag-definition") | ||
394 | 1229 | tag_ns_prefix = factory.make_name("tag-ns-prefix") | ||
395 | 1230 | tag_ns_uri = factory.make_name("tag-ns-uri") | ||
396 | 1231 | |||
397 | 1232 | consumer_key = factory.make_name("ckey") | ||
398 | 1233 | resource_token = factory.make_name("rtok") | ||
399 | 1234 | resource_secret = factory.make_name("rsec") | ||
400 | 1235 | credentials = convert_tuple_to_string( | ||
401 | 1236 | (consumer_key, resource_token, resource_secret)) | ||
402 | 1237 | |||
403 | 1238 | yield call_responder( | ||
404 | 1239 | Cluster(), cluster.EvaluateTag, { | ||
405 | 1240 | "tag_name": tag_name, | ||
406 | 1241 | "tag_definition": tag_definition, | ||
407 | 1242 | "tag_nsmap": [ | ||
408 | 1243 | {"prefix": tag_ns_prefix, "uri": tag_ns_uri}, | ||
409 | 1244 | ], | ||
410 | 1245 | "credentials": credentials, | ||
411 | 1246 | }) | ||
412 | 1247 | |||
413 | 1248 | self.assertThat(evaluate_tag, MockCalledOnceWith( | ||
414 | 1249 | tag_name, tag_definition, {tag_ns_prefix: tag_ns_uri}, | ||
415 | 1250 | (consumer_key, resource_token, resource_secret), | ||
416 | 1251 | )) | ||
417 | 1252 | |||
418 | 1253 | |||
419 | 1188 | class TestClusterProtocol_AddVirsh(MAASTestCase): | 1254 | class TestClusterProtocol_AddVirsh(MAASTestCase): |
420 | 1189 | 1255 | ||
421 | 1190 | def test__is_registered(self): | 1256 | def test__is_registered(self): |
422 | 1191 | 1257 | ||
423 | === added file 'src/provisioningserver/rpc/tests/test_tags.py' | |||
424 | --- src/provisioningserver/rpc/tests/test_tags.py 1970-01-01 00:00:00 +0000 | |||
425 | +++ src/provisioningserver/rpc/tests/test_tags.py 2014-09-23 09:14:01 +0000 | |||
426 | @@ -0,0 +1,73 @@ | |||
427 | 1 | # Copyright 2014 Canonical Ltd. This software is licensed under the | ||
428 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
429 | 3 | |||
430 | 4 | """Tests for :py:module:`~provisioningserver.rpc.dhcp`.""" | ||
431 | 5 | |||
432 | 6 | from __future__ import ( | ||
433 | 7 | absolute_import, | ||
434 | 8 | print_function, | ||
435 | 9 | unicode_literals, | ||
436 | 10 | ) | ||
437 | 11 | |||
438 | 12 | str = None | ||
439 | 13 | |||
440 | 14 | __metaclass__ = type | ||
441 | 15 | __all__ = [] | ||
442 | 16 | |||
443 | 17 | from apiclient.maas_client import ( | ||
444 | 18 | MAASClient, | ||
445 | 19 | MAASDispatcher, | ||
446 | 20 | MAASOAuth, | ||
447 | 21 | ) | ||
448 | 22 | from maastesting.factory import factory | ||
449 | 23 | from maastesting.matchers import MockCalledOnceWith | ||
450 | 24 | from maastesting.testcase import MAASTestCase | ||
451 | 25 | from mock import ( | ||
452 | 26 | ANY, | ||
453 | 27 | sentinel, | ||
454 | 28 | ) | ||
455 | 29 | from provisioningserver.rpc import tags | ||
456 | 30 | |||
457 | 31 | |||
458 | 32 | class TestEvaluateTag(MAASTestCase): | ||
459 | 33 | |||
460 | 34 | def setUp(self): | ||
461 | 35 | super(TestEvaluateTag, self).setUp() | ||
462 | 36 | get_maas_url = self.patch_autospec(tags, "get_maas_url") | ||
463 | 37 | get_maas_url.return_value = sentinel.maas_url | ||
464 | 38 | get_cluster_uuid = self.patch_autospec(tags, "get_cluster_uuid") | ||
465 | 39 | get_cluster_uuid.return_value = sentinel.cluster_uuid | ||
466 | 40 | |||
467 | 41 | def test__calls_process_node_tags(self): | ||
468 | 42 | credentials = "aaa", "bbb", "ccc" | ||
469 | 43 | process_node_tags = self.patch_autospec(tags, "process_node_tags") | ||
470 | 44 | tags.evaluate_tag( | ||
471 | 45 | sentinel.tag_name, sentinel.tag_definition, sentinel.tag_nsmap, | ||
472 | 46 | credentials) | ||
473 | 47 | self.assertThat( | ||
474 | 48 | process_node_tags, MockCalledOnceWith( | ||
475 | 49 | tag_name=sentinel.tag_name, | ||
476 | 50 | tag_definition=sentinel.tag_definition, | ||
477 | 51 | tag_nsmap=sentinel.tag_nsmap, client=ANY, | ||
478 | 52 | nodegroup_uuid=sentinel.cluster_uuid)) | ||
479 | 53 | |||
480 | 54 | def test__constructs_client_with_credentials(self): | ||
481 | 55 | consumer_key = factory.make_name("ckey") | ||
482 | 56 | resource_token = factory.make_name("rtok") | ||
483 | 57 | resource_secret = factory.make_name("rsec") | ||
484 | 58 | credentials = consumer_key, resource_token, resource_secret | ||
485 | 59 | |||
486 | 60 | self.patch_autospec(tags, "process_node_tags") | ||
487 | 61 | self.patch_autospec(tags, "MAASOAuth").side_effect = MAASOAuth | ||
488 | 62 | |||
489 | 63 | tags.evaluate_tag( | ||
490 | 64 | sentinel.tag_name, sentinel.tag_definition, sentinel.tag_nsmap, | ||
491 | 65 | credentials) | ||
492 | 66 | |||
493 | 67 | client = tags.process_node_tags.call_args[1]["client"] | ||
494 | 68 | self.assertIsInstance(client, MAASClient) | ||
495 | 69 | self.assertEqual(sentinel.maas_url, client.url) | ||
496 | 70 | self.assertIsInstance(client.dispatcher, MAASDispatcher) | ||
497 | 71 | self.assertIsInstance(client.auth, MAASOAuth) | ||
498 | 72 | self.assertThat(tags.MAASOAuth, MockCalledOnceWith( | ||
499 | 73 | consumer_key, resource_token, resource_secret)) | ||
500 | 0 | 74 | ||
501 | === modified file 'src/provisioningserver/tags.py' | |||
502 | --- src/provisioningserver/tags.py 2014-08-13 21:49:35 +0000 | |||
503 | +++ src/provisioningserver/tags.py 2014-09-23 09:14:01 +0000 | |||
504 | @@ -17,7 +17,6 @@ | |||
505 | 17 | __all__ = [ | 17 | __all__ = [ |
506 | 18 | 'merge_details', | 18 | 'merge_details', |
507 | 19 | 'merge_details_cleanly', | 19 | 'merge_details_cleanly', |
508 | 20 | 'MissingCredentials', | ||
509 | 21 | 'process_node_tags', | 20 | 'process_node_tags', |
510 | 22 | ] | 21 | ] |
511 | 23 | 22 | ||
512 | @@ -27,18 +26,8 @@ | |||
513 | 27 | import httplib | 26 | import httplib |
514 | 28 | import urllib2 | 27 | import urllib2 |
515 | 29 | 28 | ||
516 | 30 | from apiclient.maas_client import ( | ||
517 | 31 | MAASClient, | ||
518 | 32 | MAASDispatcher, | ||
519 | 33 | MAASOAuth, | ||
520 | 34 | ) | ||
521 | 35 | import bson | 29 | import bson |
522 | 36 | from lxml import etree | 30 | from lxml import etree |
523 | 37 | from provisioningserver.auth import ( | ||
524 | 38 | get_recorded_api_credentials, | ||
525 | 39 | get_recorded_nodegroup_uuid, | ||
526 | 40 | ) | ||
527 | 41 | from provisioningserver.cluster_config import get_maas_url | ||
528 | 42 | from provisioningserver.logger import get_maas_logger | 31 | from provisioningserver.logger import get_maas_logger |
529 | 43 | from provisioningserver.utils import classify | 32 | from provisioningserver.utils import classify |
530 | 44 | from provisioningserver.utils.xpath import try_match_xpath | 33 | from provisioningserver.utils.xpath import try_match_xpath |
531 | @@ -48,10 +37,6 @@ | |||
532 | 48 | maaslog = get_maas_logger("tag_processing") | 37 | maaslog = get_maas_logger("tag_processing") |
533 | 49 | 38 | ||
534 | 50 | 39 | ||
535 | 51 | class MissingCredentials(Exception): | ||
536 | 52 | """The MAAS URL or credentials are not yet set.""" | ||
537 | 53 | |||
538 | 54 | |||
539 | 55 | # An example laptop's lshw XML dump was 135kB. An example lab's LLDP | 40 | # An example laptop's lshw XML dump was 135kB. An example lab's LLDP |
540 | 56 | # XML dump was 1.6kB. A batch size of 100 would mean downloading ~14MB | 41 | # XML dump was 1.6kB. A batch size of 100 would mean downloading ~14MB |
541 | 57 | # from the region controller, which seems workable. The previous batch | 42 | # from the region controller, which seems workable. The previous batch |
542 | @@ -60,25 +45,6 @@ | |||
543 | 60 | DEFAULT_BATCH_SIZE = 100 | 45 | DEFAULT_BATCH_SIZE = 100 |
544 | 61 | 46 | ||
545 | 62 | 47 | ||
546 | 63 | def get_cached_knowledge(): | ||
547 | 64 | """Get all the information that we need to know, or raise an error. | ||
548 | 65 | |||
549 | 66 | :return: (client, nodegroup_uuid) | ||
550 | 67 | """ | ||
551 | 68 | api_credentials = get_recorded_api_credentials() | ||
552 | 69 | if api_credentials is None: | ||
553 | 70 | maaslog.error("Not updating tags: don't have API key yet.") | ||
554 | 71 | return None, None | ||
555 | 72 | nodegroup_uuid = get_recorded_nodegroup_uuid() | ||
556 | 73 | if nodegroup_uuid is None: | ||
557 | 74 | maaslog.error("Not updating tags: don't have UUID yet.") | ||
558 | 75 | return None, None | ||
559 | 76 | client = MAASClient( | ||
560 | 77 | MAASOAuth(*api_credentials), MAASDispatcher(), | ||
561 | 78 | get_maas_url()) | ||
562 | 79 | return client, nodegroup_uuid | ||
563 | 80 | |||
564 | 81 | |||
565 | 82 | # A content-type: function mapping that can decode data of that type. | 48 | # A content-type: function mapping that can decode data of that type. |
566 | 83 | decoders = { | 49 | decoders = { |
567 | 84 | "application/json": lambda data: json.loads(data), | 50 | "application/json": lambda data: json.loads(data), |
568 | @@ -346,20 +312,18 @@ | |||
569 | 346 | nodes_matched, nodes_unmatched) | 312 | nodes_matched, nodes_unmatched) |
570 | 347 | 313 | ||
571 | 348 | 314 | ||
573 | 349 | def process_node_tags(tag_name, tag_definition, tag_nsmap, batch_size=None): | 315 | def process_node_tags( |
574 | 316 | tag_name, tag_definition, tag_nsmap, | ||
575 | 317 | client, nodegroup_uuid, batch_size=None): | ||
576 | 350 | """Update the nodes for a new/changed tag definition. | 318 | """Update the nodes for a new/changed tag definition. |
577 | 351 | 319 | ||
578 | 320 | :param client: A `MAASClient` used to fetch the node's details via | ||
579 | 321 | calls to the web API. | ||
580 | 322 | :param nodegroup_uuid: The UUID for this cluster. | ||
581 | 352 | :param tag_name: Name of the tag to update nodes for | 323 | :param tag_name: Name of the tag to update nodes for |
582 | 353 | :param tag_definition: Tag definition | 324 | :param tag_definition: Tag definition |
583 | 354 | :param batch_size: Size of batch | 325 | :param batch_size: Size of batch |
584 | 355 | """ | 326 | """ |
585 | 356 | client, nodegroup_uuid = get_cached_knowledge() | ||
586 | 357 | if not all([client, nodegroup_uuid]): | ||
587 | 358 | maaslog.error( | ||
588 | 359 | "Unable to update tag: %s for definition %r. " | ||
589 | 360 | "Please refresh secrets, then rebuild this tag." | ||
590 | 361 | % (tag_name, tag_definition)) | ||
591 | 362 | raise MissingCredentials() | ||
592 | 363 | # We evaluate this early, so we can fail before sending a bunch of data to | 327 | # We evaluate this early, so we can fail before sending a bunch of data to |
593 | 364 | # the server | 328 | # the server |
594 | 365 | xpath = etree.XPath(tag_definition, namespaces=tag_nsmap) | 329 | xpath = etree.XPath(tag_definition, namespaces=tag_nsmap) |
595 | 366 | 330 | ||
596 | === modified file 'src/provisioningserver/tasks.py' | |||
597 | --- src/provisioningserver/tasks.py 2014-09-17 08:51:31 +0000 | |||
598 | +++ src/provisioningserver/tasks.py 2014-09-23 09:14:01 +0000 | |||
599 | @@ -28,10 +28,7 @@ | |||
600 | 28 | 28 | ||
601 | 29 | from celery.app import app_or_default | 29 | from celery.app import app_or_default |
602 | 30 | from celery.task import task | 30 | from celery.task import task |
607 | 31 | from provisioningserver import ( | 31 | from provisioningserver import boot_images |
604 | 32 | boot_images, | ||
605 | 33 | tags, | ||
606 | 34 | ) | ||
608 | 35 | from provisioningserver.auth import ( | 32 | from provisioningserver.auth import ( |
609 | 36 | record_api_credentials, | 33 | record_api_credentials, |
610 | 37 | record_nodegroup_uuid, | 34 | record_nodegroup_uuid, |
611 | @@ -242,35 +239,3 @@ | |||
612 | 242 | def report_boot_images(): | 239 | def report_boot_images(): |
613 | 243 | """For master worker only: report available netboot images.""" | 240 | """For master worker only: report available netboot images.""" |
614 | 244 | boot_images.report_to_server() | 241 | boot_images.report_to_server() |
615 | 245 | |||
616 | 246 | |||
617 | 247 | # How many times should a update node tags task be retried? | ||
618 | 248 | UPDATE_NODE_TAGS_MAX_RETRY = 10 | ||
619 | 249 | |||
620 | 250 | # How long to wait between update node tags task retries (in seconds)? | ||
621 | 251 | UPDATE_NODE_TAGS_RETRY_DELAY = 2 | ||
622 | 252 | |||
623 | 253 | |||
624 | 254 | # ===================================================================== | ||
625 | 255 | # Tags-related tasks | ||
626 | 256 | # ===================================================================== | ||
627 | 257 | |||
628 | 258 | |||
629 | 259 | @task(max_retries=UPDATE_NODE_TAGS_MAX_RETRY) | ||
630 | 260 | @log_call() | ||
631 | 261 | @log_exception_text | ||
632 | 262 | def update_node_tags(tag_name, tag_definition, tag_nsmap, retry=True): | ||
633 | 263 | """Update the nodes for a new/changed tag definition. | ||
634 | 264 | |||
635 | 265 | :param tag_name: Name of the tag to update nodes for | ||
636 | 266 | :param tag_definition: Tag definition | ||
637 | 267 | :param retry: Whether to retry on failure | ||
638 | 268 | """ | ||
639 | 269 | try: | ||
640 | 270 | tags.process_node_tags(tag_name, tag_definition, tag_nsmap) | ||
641 | 271 | except tags.MissingCredentials, exc: | ||
642 | 272 | if retry: | ||
643 | 273 | return update_node_tags.retry( | ||
644 | 274 | exc=exc, countdown=UPDATE_NODE_TAGS_RETRY_DELAY) | ||
645 | 275 | else: | ||
646 | 276 | raise | ||
647 | 277 | 242 | ||
648 | === modified file 'src/provisioningserver/tests/test_tags.py' | |||
649 | --- src/provisioningserver/tests/test_tags.py 2014-09-11 09:19:44 +0000 | |||
650 | +++ src/provisioningserver/tests/test_tags.py 2014-09-23 09:14:01 +0000 | |||
651 | @@ -37,7 +37,6 @@ | |||
652 | 37 | sentinel, | 37 | sentinel, |
653 | 38 | ) | 38 | ) |
654 | 39 | from provisioningserver import tags | 39 | from provisioningserver import tags |
655 | 40 | from provisioningserver.auth import get_recorded_nodegroup_uuid | ||
656 | 41 | from provisioningserver.testing.testcase import PservTestCase | 40 | from provisioningserver.testing.testcase import PservTestCase |
657 | 42 | from testtools.matchers import ( | 41 | from testtools.matchers import ( |
658 | 43 | DocTestMatches, | 42 | DocTestMatches, |
659 | @@ -484,29 +483,6 @@ | |||
660 | 484 | super(TestTagUpdating, self).setUp() | 483 | super(TestTagUpdating, self).setUp() |
661 | 485 | self.useFixture(FakeLogger()) | 484 | self.useFixture(FakeLogger()) |
662 | 486 | 485 | ||
663 | 487 | def test_get_cached_knowledge_knows_nothing(self): | ||
664 | 488 | # If we haven't given it any secrets, we should get back nothing | ||
665 | 489 | self.assertEqual((None, None), tags.get_cached_knowledge()) | ||
666 | 490 | |||
667 | 491 | def test_get_cached_knowledge_with_only_url(self): | ||
668 | 492 | self.set_maas_url() | ||
669 | 493 | self.assertEqual((None, None), tags.get_cached_knowledge()) | ||
670 | 494 | |||
671 | 495 | def test_get_cached_knowledge_with_only_url_creds(self): | ||
672 | 496 | self.set_maas_url() | ||
673 | 497 | self.set_api_credentials() | ||
674 | 498 | self.assertEqual((None, None), tags.get_cached_knowledge()) | ||
675 | 499 | |||
676 | 500 | def test_get_cached_knowledge_with_all_info(self): | ||
677 | 501 | self.set_maas_url() | ||
678 | 502 | self.set_api_credentials() | ||
679 | 503 | self.set_node_group_uuid() | ||
680 | 504 | client, uuid = tags.get_cached_knowledge() | ||
681 | 505 | self.assertIsNot(None, client) | ||
682 | 506 | self.assertIsInstance(client, MAASClient) | ||
683 | 507 | self.assertIsNot(None, uuid) | ||
684 | 508 | self.assertEqual(get_recorded_nodegroup_uuid(), uuid) | ||
685 | 509 | |||
686 | 510 | def fake_client(self): | 486 | def fake_client(self): |
687 | 511 | return MAASClient(None, None, self.make_maas_url()) | 487 | return MAASClient(None, None, self.make_maas_url()) |
688 | 512 | 488 | ||
689 | @@ -615,16 +591,6 @@ | |||
690 | 615 | (['a', 'c'], ['b']), | 591 | (['a', 'c'], ['b']), |
691 | 616 | tags.classify(xpath, node_details)) | 592 | tags.classify(xpath, node_details)) |
692 | 617 | 593 | ||
693 | 618 | def test_process_node_tags_no_secrets(self): | ||
694 | 619 | self.patch(MAASClient, 'get') | ||
695 | 620 | self.patch(MAASClient, 'post') | ||
696 | 621 | tag_name = factory.make_name('tag') | ||
697 | 622 | self.assertRaises( | ||
698 | 623 | tags.MissingCredentials, | ||
699 | 624 | tags.process_node_tags, tag_name, '//node', None) | ||
700 | 625 | self.assertFalse(MAASClient.get.called) | ||
701 | 626 | self.assertFalse(MAASClient.post.called) | ||
702 | 627 | |||
703 | 628 | def test_process_node_tags_integration(self): | 594 | def test_process_node_tags_integration(self): |
704 | 629 | self.set_secrets() | 595 | self.set_secrets() |
705 | 630 | get_nodes = FakeMethod( | 596 | get_nodes = FakeMethod( |
706 | @@ -653,10 +619,12 @@ | |||
707 | 653 | self.patch(MAASClient, 'get', get_fake) | 619 | self.patch(MAASClient, 'get', get_fake) |
708 | 654 | self.patch(MAASClient, 'post', post_fake) | 620 | self.patch(MAASClient, 'post', post_fake) |
709 | 655 | tag_name = factory.make_name('tag') | 621 | tag_name = factory.make_name('tag') |
711 | 656 | nodegroup_uuid = get_recorded_nodegroup_uuid() | 622 | nodegroup_uuid = factory.make_name("nodegroup-uuid") |
712 | 657 | tag_definition = '//lshw:node' | 623 | tag_definition = '//lshw:node' |
713 | 658 | tag_nsmap = {"lshw": "lshw"} | 624 | tag_nsmap = {"lshw": "lshw"} |
715 | 659 | tags.process_node_tags(tag_name, tag_definition, tag_nsmap=tag_nsmap) | 625 | tags.process_node_tags( |
716 | 626 | tag_name, tag_definition, tag_nsmap, | ||
717 | 627 | self.fake_client(), nodegroup_uuid) | ||
718 | 660 | nodegroup_url = '/api/1.0/nodegroups/%s/' % (nodegroup_uuid,) | 628 | nodegroup_url = '/api/1.0/nodegroups/%s/' % (nodegroup_uuid,) |
719 | 661 | tag_url = '/api/1.0/tags/%s/' % (tag_name,) | 629 | tag_url = '/api/1.0/tags/%s/' % (tag_name,) |
720 | 662 | self.assertEqual( | 630 | self.assertEqual( |
721 | @@ -687,9 +655,6 @@ | |||
722 | 687 | client = object() | 655 | client = object() |
723 | 688 | uuid = factory.make_name('nodegroupuuid') | 656 | uuid = factory.make_name('nodegroupuuid') |
724 | 689 | self.patch( | 657 | self.patch( |
725 | 690 | tags, 'get_cached_knowledge', | ||
726 | 691 | MagicMock(return_value=(client, uuid))) | ||
727 | 692 | self.patch( | ||
728 | 693 | tags, 'get_nodes_for_node_group', | 658 | tags, 'get_nodes_for_node_group', |
729 | 694 | MagicMock(return_value=['a', 'b', 'c'])) | 659 | MagicMock(return_value=['a', 'b', 'c'])) |
730 | 695 | fake_first = FakeMethod(result={ | 660 | fake_first = FakeMethod(result={ |
731 | @@ -706,8 +671,8 @@ | |||
732 | 706 | tag_name = factory.make_name('tag') | 671 | tag_name = factory.make_name('tag') |
733 | 707 | tag_definition = '//node' | 672 | tag_definition = '//node' |
734 | 708 | tags.process_node_tags( | 673 | tags.process_node_tags( |
737 | 709 | tag_name, tag_definition, tag_nsmap=None, batch_size=2) | 674 | tag_name, tag_definition, tag_nsmap=None, client=client, |
738 | 710 | tags.get_cached_knowledge.assert_called_once_with() | 675 | nodegroup_uuid=uuid, batch_size=2) |
739 | 711 | tags.get_nodes_for_node_group.assert_called_once_with(client, uuid) | 676 | tags.get_nodes_for_node_group.assert_called_once_with(client, uuid) |
740 | 712 | self.assertEqual([((client, uuid, ['a', 'c']), {})], fake_first.calls) | 677 | self.assertEqual([((client, uuid, ['a', 'c']), {})], fake_first.calls) |
741 | 713 | self.assertEqual([((client, uuid, ['b']), {})], fake_second.calls) | 678 | self.assertEqual([((client, uuid, ['b']), {})], fake_second.calls) |
742 | 714 | 679 | ||
743 | === modified file 'src/provisioningserver/tests/test_tasks.py' | |||
744 | --- src/provisioningserver/tests/test_tasks.py 2014-09-15 14:28:28 +0000 | |||
745 | +++ src/provisioningserver/tests/test_tasks.py 2014-09-23 09:14:01 +0000 | |||
746 | @@ -37,7 +37,6 @@ | |||
747 | 37 | auth, | 37 | auth, |
748 | 38 | boot_images, | 38 | boot_images, |
749 | 39 | cache, | 39 | cache, |
750 | 40 | tags, | ||
751 | 41 | tasks, | 40 | tasks, |
752 | 42 | ) | 41 | ) |
753 | 43 | from provisioningserver.boot import tftppath | 42 | from provisioningserver.boot import tftppath |
754 | @@ -52,15 +51,12 @@ | |||
755 | 52 | DNSForwardZoneConfig, | 51 | DNSForwardZoneConfig, |
756 | 53 | DNSReverseZoneConfig, | 52 | DNSReverseZoneConfig, |
757 | 54 | ) | 53 | ) |
758 | 55 | from provisioningserver.tags import MissingCredentials | ||
759 | 56 | from provisioningserver.tasks import ( | 54 | from provisioningserver.tasks import ( |
760 | 57 | refresh_secrets, | 55 | refresh_secrets, |
761 | 58 | report_boot_images, | 56 | report_boot_images, |
762 | 59 | rndc_command, | 57 | rndc_command, |
763 | 60 | RNDC_COMMAND_MAX_RETRY, | 58 | RNDC_COMMAND_MAX_RETRY, |
764 | 61 | setup_rndc_configuration, | 59 | setup_rndc_configuration, |
765 | 62 | update_node_tags, | ||
766 | 63 | UPDATE_NODE_TAGS_MAX_RETRY, | ||
767 | 64 | write_dns_config, | 60 | write_dns_config, |
768 | 65 | write_dns_zone_config, | 61 | write_dns_zone_config, |
769 | 66 | write_full_dns_config, | 62 | write_full_dns_config, |
770 | @@ -348,44 +344,3 @@ | |||
771 | 348 | 344 | ||
772 | 349 | args, kwargs = MAASClient.post.call_args | 345 | args, kwargs = MAASClient.post.call_args |
773 | 350 | self.assertItemsEqual([image], json.loads(kwargs['images'])) | 346 | self.assertItemsEqual([image], json.loads(kwargs['images'])) |
774 | 351 | |||
775 | 352 | |||
776 | 353 | class TestTagTasks(PservTestCase): | ||
777 | 354 | |||
778 | 355 | def setUp(self): | ||
779 | 356 | super(TestTagTasks, self).setUp() | ||
780 | 357 | self.celery = self.useFixture(CeleryFixture()) | ||
781 | 358 | |||
782 | 359 | def test_update_node_tags_can_be_retried(self): | ||
783 | 360 | self.set_secrets() | ||
784 | 361 | # The update_node_tags task can be retried. | ||
785 | 362 | # Simulate a temporary failure. | ||
786 | 363 | number_of_failures = UPDATE_NODE_TAGS_MAX_RETRY | ||
787 | 364 | raised_exception = MissingCredentials( | ||
788 | 365 | factory.make_name('exception'), random.randint(100, 200)) | ||
789 | 366 | simulate_failures = MultiFakeMethod( | ||
790 | 367 | [FakeMethod(failure=raised_exception)] * number_of_failures + | ||
791 | 368 | [FakeMethod()]) | ||
792 | 369 | self.patch(tags, 'process_node_tags', simulate_failures) | ||
793 | 370 | tag = factory.make_string() | ||
794 | 371 | result = update_node_tags.delay( | ||
795 | 372 | tag, '//node', tag_nsmap=None, retry=True) | ||
796 | 373 | assertTaskRetried( | ||
797 | 374 | self, result, UPDATE_NODE_TAGS_MAX_RETRY + 1, | ||
798 | 375 | 'provisioningserver.tasks.update_node_tags') | ||
799 | 376 | |||
800 | 377 | def test_update_node_tags_is_retried_a_limited_number_of_times(self): | ||
801 | 378 | self.set_secrets() | ||
802 | 379 | # If we simulate UPDATE_NODE_TAGS_MAX_RETRY + 1 failures, the | ||
803 | 380 | # task fails. | ||
804 | 381 | number_of_failures = UPDATE_NODE_TAGS_MAX_RETRY + 1 | ||
805 | 382 | raised_exception = MissingCredentials( | ||
806 | 383 | factory.make_name('exception'), random.randint(100, 200)) | ||
807 | 384 | simulate_failures = MultiFakeMethod( | ||
808 | 385 | [FakeMethod(failure=raised_exception)] * number_of_failures + | ||
809 | 386 | [FakeMethod()]) | ||
810 | 387 | self.patch(tags, 'process_node_tags', simulate_failures) | ||
811 | 388 | tag = factory.make_string() | ||
812 | 389 | self.assertRaises( | ||
813 | 390 | MissingCredentials, update_node_tags.delay, tag, | ||
814 | 391 | '//node', tag_nsmap=None, retry=True) |
Looking good so far. One minor nitpick, but otherwise it's cool beans.