14
14
__metaclass__ = type
17
from itertools import izip
19
from fixtures import FakeLogger
17
20
from maasserver import populate_tags as populate_tags_module
21
from maasserver.enum import NODEGROUP_STATUS
18
22
from maasserver.models import Tag
19
23
from maasserver.populate_tags import (
25
_get_clients_for_populating_tags,
21
27
populate_tags_for_single_node,
29
from maasserver.rpc.testing.fixtures import MockLiveRegionToClusterRPCFixture
30
from maasserver.testing.eventloop import (
31
RegionEventLoopFixture,
32
RunningEventLoopFixture,
24
34
from maasserver.testing.factory import factory
25
35
from maasserver.testing.testcase import MAASServerTestCase
36
from maastesting.matchers import MockCalledOnceWith
26
37
from metadataserver.models import commissioningscript
43
from provisioningserver.rpc.cluster import EvaluateTag
44
from provisioningserver.rpc.common import Client
45
from provisioningserver.rpc.testing import (
49
from provisioningserver.utils.twisted import asynchronous
50
from testtools.deferredruntest import extract_result
51
from testtools.monkey import MonkeyPatcher
52
from twisted.internet import defer
55
def make_accepted_NodeGroup():
56
return factory.make_NodeGroup(status=NODEGROUP_STATUS.ACCEPTED)
59
def make_Tag_without_populating():
60
# Create a tag but prevent evaluation when saving.
61
dont_populate = MonkeyPatcher((Tag, "populate_nodes", lambda self: None))
62
return dont_populate.run_with_patches(factory.make_Tag)
65
class TestGetClientsForPopulatingTags(MAASServerTestCase):
67
def test__returns_no_clients_when_there_are_no_clusters(self):
68
tag_name = factory.make_name("tag")
69
clients = _get_clients_for_populating_tags([], tag_name)
70
self.assertEqual([], clients)
72
def patch_getClientFor(self):
73
return self.patch_autospec(populate_tags_module, "getClientFor")
75
def test__returns_no_clients_when_there_is_an_error(self):
76
nodegroup_with_connection = make_accepted_NodeGroup()
77
nodegroup_without_connection = make_accepted_NodeGroup()
79
def getClientFor(uuid, timeout):
80
if uuid == nodegroup_with_connection.uuid:
81
return defer.succeed(sentinel.client)
83
return defer.fail(ZeroDivisionError())
84
self.patch_getClientFor().side_effect = getClientFor
86
tag_name = factory.make_name("tag")
88
(nodegroup_with_connection.uuid,
89
nodegroup_with_connection.cluster_name),
90
(nodegroup_without_connection.uuid,
91
nodegroup_without_connection.cluster_name),
93
clients = _get_clients_for_populating_tags(clusters, tag_name)
94
self.assertEqual([sentinel.client], clients)
96
def test__logs_errors_obtaining_clients(self):
97
getClientFor = self.patch_getClientFor()
98
getClientFor.side_effect = always_fail_with(
99
ZeroDivisionError("an error message one would surmise"))
100
nodegroup = make_accepted_NodeGroup()
101
tag_name = factory.make_name("tag")
102
clusters = [(nodegroup.uuid, nodegroup.cluster_name)]
103
with FakeLogger("maas") as log:
104
_get_clients_for_populating_tags(clusters, tag_name)
105
self.assertDocTestMatches(
106
"Cannot evaluate tag ... on cluster ... (...): ... surmise",
109
def test__waits_for_clients_for_30_seconds_by_default(self):
110
getClientFor = self.patch_getClientFor()
111
getClientFor.side_effect = always_succeed_with(sentinel.client)
112
nodegroup = make_accepted_NodeGroup()
113
tag_name = factory.make_name("tag")
114
clusters = [(nodegroup.uuid, nodegroup.cluster_name)]
115
clients = _get_clients_for_populating_tags(clusters, tag_name)
116
self.assertEqual([sentinel.client], clients)
118
getClientFor, MockCalledOnceWith(
119
nodegroup.uuid, timeout=30))
121
def test__obtains_multiple_clients(self):
122
getClientFor = self.patch_getClientFor()
123
# Return a 2-tuple as a stand-in for a real client.
124
getClientFor.side_effect = lambda uuid, timeout: (
125
defer.succeed((sentinel.client, uuid)))
126
nodegroups = [make_accepted_NodeGroup() for _ in xrange(3)]
127
tag_name = factory.make_name("tag")
128
clusters = [(ng.uuid, ng.cluster_name) for ng in nodegroups]
129
clients = _get_clients_for_populating_tags(clusters, tag_name)
130
self.assertItemsEqual(
131
[(sentinel.client, nodegroup.uuid) for nodegroup in nodegroups],
135
class TestDoPopulateTags(MAASServerTestCase):
137
def patch_clients(self, nodegroups):
138
clients = [create_autospec(Client, instance=True) for _ in nodegroups]
139
for nodegroup, client in izip(nodegroups, clients):
140
client.side_effect = always_succeed_with(None)
141
client.ident = nodegroup.uuid
143
_get_clients = self.patch_autospec(
144
populate_tags_module, "_get_clients_for_populating_tags")
145
_get_clients.return_value = defer.succeed(clients)
149
def test__makes_calls_to_each_client_given(self):
150
nodegroups = [make_accepted_NodeGroup() for _ in xrange(3)]
151
clients = self.patch_clients(nodegroups)
153
tag_name = factory.make_name("tag")
154
tag_definition = factory.make_name("definition")
155
tag_nsmap_prefix = factory.make_name("prefix")
156
tag_nsmap_uri = factory.make_name("uri")
157
tag_nsmap = {tag_nsmap_prefix: tag_nsmap_uri}
160
(ng.uuid, ng.cluster_name, ng.api_credentials)
161
for ng in nodegroups)
163
[d] = _do_populate_tags(
164
clusters, tag_name, tag_definition, tag_nsmap)
166
self.assertIsNone(extract_result(d))
168
for nodegroup, client in izip(nodegroups, clients):
169
self.expectThat(client, MockCalledOnceWith(
170
EvaluateTag, tag_name=tag_name, tag_definition=tag_definition,
171
tag_nsmap=[{"prefix": tag_nsmap_prefix, "uri": tag_nsmap_uri}],
172
credentials=nodegroup.api_credentials))
174
def test__logs_successes(self):
175
nodegroups = [make_accepted_NodeGroup()]
176
self.patch_clients(nodegroups)
178
tag_name = factory.make_name("tag")
179
tag_definition = factory.make_name("definition")
183
(ng.uuid, ng.cluster_name, ng.api_credentials)
184
for ng in nodegroups)
186
with FakeLogger("maas") as log:
187
[d] = _do_populate_tags(
188
clusters, tag_name, tag_definition, tag_nsmap)
189
self.assertIsNone(extract_result(d))
191
self.assertDocTestMatches(
192
"Tag tag-... (definition-...) evaluated on cluster ... (...)",
195
def test__logs_failures(self):
196
nodegroups = [make_accepted_NodeGroup()]
197
[client] = self.patch_clients(nodegroups)
198
client.side_effect = always_fail_with(
199
ZeroDivisionError("splendid day for a spot of cricket"))
201
tag_name = factory.make_name("tag")
202
tag_definition = factory.make_name("definition")
206
(ng.uuid, ng.cluster_name, ng.api_credentials)
207
for ng in nodegroups)
209
with FakeLogger("maas") as log:
210
[d] = _do_populate_tags(
211
clusters, tag_name, tag_definition, tag_nsmap)
212
self.assertIsNone(extract_result(d))
214
self.assertDocTestMatches(
215
"Tag tag-... (definition-...) could not be evaluated ... (...): "
216
"splendid day for a spot of cricket", log.output)
30
219
class TestPopulateTags(MAASServerTestCase):
32
def test_populate_tags_task_routed_to_nodegroup_worker(self):
33
nodegroup = factory.make_node_group()
34
tag = factory.make_tag()
35
task = self.patch(populate_tags_module, 'update_node_tags')
37
args, kwargs = task.apply_async.call_args
38
self.assertEqual(nodegroup.work_queue, kwargs['queue'])
40
def test_populate_tags_task_routed_to_all_nodegroup_workers(self):
41
nodegroups = [factory.make_node_group() for i in range(5)]
42
tag = factory.make_tag()
43
refresh = self.patch(populate_tags_module, 'refresh_worker')
44
task = self.patch(populate_tags_module, 'update_node_tags')
46
refresh_calls = [mock.call(nodegroup) for nodegroup in nodegroups]
47
refresh.assert_has_calls(refresh_calls, any_order=True)
50
queue=nodegroup.work_queue,
53
'tag_definition': tag.definition,
54
'tag_nsmap': tag_nsmap,
57
for nodegroup in nodegroups
59
task.apply_async.assert_has_calls(task_calls, any_order=True)
221
def patch_do_populate_tags(self):
222
do_populate_tags = self.patch_autospec(
223
populate_tags_module, "_do_populate_tags")
224
do_populate_tags.return_value = [sentinel.d]
225
return do_populate_tags
227
def test__calls_do_populate_tags_with_no_clusters(self):
228
do_populate_tags = self.patch_do_populate_tags()
229
tag = make_Tag_without_populating()
231
self.assertThat(do_populate_tags, MockCalledOnceWith(
232
(), tag.name, tag.definition, populate_tags_module.tag_nsmap))
234
def test__calls_do_populate_tags_with_clusters(self):
235
do_populate_tags = self.patch_do_populate_tags()
236
nodegroups = [make_accepted_NodeGroup() for _ in xrange(3)]
237
tag = make_Tag_without_populating()
239
clusters_expected = tuple(
240
(ng.uuid, ng.cluster_name, ng.api_credentials)
241
for ng in nodegroups)
242
self.assertThat(do_populate_tags, MockCalledOnceWith(
243
clusters_expected, tag.name, tag.definition,
244
populate_tags_module.tag_nsmap))
247
class TestPopulateTagsEndToNearlyEnd(MAASServerTestCase):
249
def prepare_live_rpc(self):
250
self.useFixture(RegionEventLoopFixture("rpc"))
251
self.useFixture(RunningEventLoopFixture())
252
return self.useFixture(MockLiveRegionToClusterRPCFixture())
254
def test__calls_are_made_to_all_clusters(self):
255
rpc_fixture = self.prepare_live_rpc()
256
nodegroups = [make_accepted_NodeGroup() for _ in xrange(3)]
258
for nodegroup in nodegroups:
259
protocol = rpc_fixture.makeCluster(nodegroup, EvaluateTag)
260
protocol.EvaluateTag.side_effect = always_succeed_with({})
261
protocols.append(protocol)
262
tag = make_Tag_without_populating()
264
d = populate_tags(tag)
266
# `d` is a testing-only convenience. We must wait for it to fire, and
267
# we must do that from the reactor thread.
268
wait_for_populate = asynchronous(lambda: d)
269
wait_for_populate().wait(10)
271
for nodegroup, protocol in izip(nodegroups, protocols):
272
self.expectThat(protocol.EvaluateTag, MockCalledOnceWith(
273
protocol, tag_name=tag.name, tag_definition=tag.definition,
274
tag_nsmap=ANY, credentials=nodegroup.api_credentials))
62
277
class TestPopulateTagsForSingleNode(MAASServerTestCase):
64
279
def test_updates_node_with_all_applicable_tags(self):
65
node = factory.make_node()
66
factory.make_node_commission_result(
280
node = factory.make_Node()
281
factory.make_NodeResult_for_commissioning(
67
282
node, commissioningscript.LSHW_OUTPUT_NAME, 0, b"<foo/>")
68
factory.make_node_commission_result(
283
factory.make_NodeResult_for_commissioning(
69
284
node, commissioningscript.LLDP_OUTPUT_NAME, 0, b"<bar/>")
71
factory.make_tag("foo", "/foo"),
72
factory.make_tag("bar", "//lldp:bar"),
73
factory.make_tag("baz", "/foo/bar"),
286
factory.make_Tag("foo", "/foo"),
287
factory.make_Tag("bar", "//lldp:bar"),
288
factory.make_Tag("baz", "/foo/bar"),
75
290
populate_tags_for_single_node(tags, node)
76
291
self.assertItemsEqual(
77
292
["foo", "bar"], [tag.name for tag in node.tags.all()])
79
294
def test_ignores_tags_with_unrecognised_namespaces(self):
80
node = factory.make_node()
81
factory.make_node_commission_result(
295
node = factory.make_Node()
296
factory.make_NodeResult_for_commissioning(
82
297
node, commissioningscript.LSHW_OUTPUT_NAME, 0, b"<foo/>")
84
factory.make_tag("foo", "/foo"),
85
factory.make_tag("lou", "//nge:bar"),
299
factory.make_Tag("foo", "/foo"),
300
factory.make_Tag("lou", "//nge:bar"),
87
302
populate_tags_for_single_node(tags, node) # Look mom, no exception!
88
303
self.assertSequenceEqual(
89
304
["foo"], [tag.name for tag in node.tags.all()])
91
306
def test_ignores_tags_without_definition(self):
92
node = factory.make_node()
93
factory.make_node_commission_result(
307
node = factory.make_Node()
308
factory.make_NodeResult_for_commissioning(
94
309
node, commissioningscript.LSHW_OUTPUT_NAME, 0, b"<foo/>")
96
factory.make_tag("foo", "/foo"),
311
factory.make_Tag("foo", "/foo"),
97
312
Tag(name="empty", definition=""),
98
313
Tag(name="null", definition=None),