blob: 01ba3540bec1b2d3e3a1ea7a52b7a87affd4377c [file] [log] [blame]
Scott Baker13e953c2018-05-17 09:19:15 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
Scott Baker987748d2018-08-09 16:14:11 -070016import json
Scott Baker13e953c2018-05-17 09:19:15 -070017import os, sys
18import unittest
19from mock import patch, PropertyMock, ANY, MagicMock
20from unit_test_common import setup_sync_unit_test
21
22def fake_init_kubernetes_client(self):
23 self.v1core = MagicMock()
24 self.v1apps = MagicMock()
25 self.v1batch = MagicMock()
26
27class TestPullPods(unittest.TestCase):
28
29 def setUp(self):
30 self.unittest_setup = setup_sync_unit_test(os.path.abspath(os.path.dirname(os.path.realpath(__file__))),
31 globals(),
32 [("kubernetes-service", "kubernetes.proto")] )
33
34 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(os.path.realpath(__file__))), "../pull_steps"))
35
36 from pull_pods import KubernetesServiceInstancePullStep
37 self.pull_step_class = KubernetesServiceInstancePullStep
38
39 self.service = KubernetesService()
40 self.trust_domain = TrustDomain(name="test-trust", owner=self.service)
41 self.principal = Principal(name="test-principal", trust_domain = self.trust_domain)
42 self.image = Image(name="test-image", tag="1.1", kind="container")
43
44 def tearDown(self):
45 sys.path = self.unittest_setup["sys_path_save"]
46
47 def test_read_obj_kind(self):
48 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client):
49 pull_step = self.pull_step_class()
50 pull_step.v1apps.read_namespaced_replica_set.return_value = ["my_replica_set"]
51 pull_step.v1apps.read_namespaced_stateful_set.return_value = ["my_stateful_set"]
52 pull_step.v1apps.read_namespaced_daemon_set.return_value = ["my_daemon_set"]
53 pull_step.v1apps.read_namespaced_deployment.return_value = ["my_deployment"]
54 pull_step.v1batch.read_namespaced_job.return_value = ["my_job"]
55
56 obj = pull_step.read_obj_kind("ReplicaSet", "foo", self.trust_domain)
57 self.assertEqual(obj, ["my_replica_set"])
58
59 obj = pull_step.read_obj_kind("StatefulSet", "foo", self.trust_domain)
60 self.assertEqual(obj, ["my_stateful_set"])
61
62 obj = pull_step.read_obj_kind("DaemonSet", "foo", self.trust_domain)
63 self.assertEqual(obj, ["my_daemon_set"])
64
65 obj = pull_step.read_obj_kind("Deployment", "foo", self.trust_domain)
66 self.assertEqual(obj, ["my_deployment"])
67
68 obj = pull_step.read_obj_kind("Job", "foo", self.trust_domain)
69 self.assertEqual(obj, ["my_job"])
70
71 def test_get_controller_from_obj(self):
72 """ Setup an owner_reference chain: leaf --> StatefulSet --> Deployment. Calling get_controller_from_obj()
73 on the leaf should return the deployment.
74 """
75 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client):
76 leaf_obj = MagicMock()
77 leaf_obj.metadata.owner_references= [MagicMock(controller=True, name="my_stateful_set", kind="StatefulSet")]
78
79 ss_obj = MagicMock()
80 ss_obj.metadata.owner_references= [MagicMock(controller=True, name="my_deployment", kind="Deployment")]
81
82 dep_obj = MagicMock()
83 dep_obj.metadata.owner_references = []
84
85 pull_step = self.pull_step_class()
86 pull_step.v1apps.read_namespaced_stateful_set.return_value = ss_obj
87 pull_step.v1apps.read_namespaced_deployment.return_value = dep_obj
88
89 controller = pull_step.get_controller_from_obj(leaf_obj, self.trust_domain)
90 self.assertEqual(controller, dep_obj)
91
92 def test_get_slice_from_pod_exists(self):
93 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client),\
94 patch.object(self.pull_step_class, "get_controller_from_obj") as get_controller_from_obj, \
95 patch.object(Slice.objects, "get_items") as slice_objects:
96 pull_step = self.pull_step_class()
97
98 myslice = Slice(name="myslice")
99
100 dep_obj = MagicMock()
101 dep_obj.metadata.name = myslice.name
102 get_controller_from_obj.return_value = dep_obj
103
104 slice_objects.return_value = [myslice]
105
106 pod = MagicMock()
107
108 slice = pull_step.get_slice_from_pod(pod, self.trust_domain, self.principal)
109 self.assertEqual(slice, myslice)
110
111 def test_get_slice_from_pod_noexist(self):
112 """ Call get_slice_from_pod() where not pre-existing slice is present. A new slice will be created, named
113 after the pod's controller.
114 """
115 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client),\
116 patch.object(self.pull_step_class, "get_controller_from_obj") as get_controller_from_obj, \
117 patch.object(Site.objects, "get_items") as site_objects:
118 pull_step = self.pull_step_class()
119
120 site_objects.return_value=[Site(name="mysite")]
121
122 dep_obj = MagicMock()
123 dep_obj.metadata.name = "my_other_slice"
124 get_controller_from_obj.return_value = dep_obj
125
126 pod = MagicMock()
127
128 slice = pull_step.get_slice_from_pod(pod, self.trust_domain, self.principal)
129 self.assertEqual(slice.name, "my_other_slice")
130 self.assertEqual(slice.trust_domain, self.trust_domain)
131 self.assertEqual(slice.principal, self.principal)
132 self.assertEqual(slice.xos_managed, False)
133
134 def test_get_trustdomain_from_pod_exists(self):
135 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client), \
136 patch.object(TrustDomain.objects, "get_items") as trustdomain_objects:
137 pull_step = self.pull_step_class()
138
139 pod = MagicMock()
140 pod.metadata.namespace = self.trust_domain.name
141
142 trustdomain_objects.return_value = [self.trust_domain]
143
144 trustdomain = pull_step.get_trustdomain_from_pod(pod, owner_service=self.service)
145 self.assertEqual(trustdomain, self.trust_domain)
146
147 def test_get_trustdomain_from_pod_noexist(self):
148 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client):
149 pull_step = self.pull_step_class()
150
151 pod = MagicMock()
152 pod.metadata.namespace = "new-trust"
153
154 trustdomain = pull_step.get_trustdomain_from_pod(pod, owner_service=self.service)
155 self.assertEqual(trustdomain.name, "new-trust")
156 self.assertEqual(trustdomain.owner, self.service)
157
158 def test_get_principal_from_pod_exists(self):
159 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client), \
160 patch.object(Principal.objects, "get_items") as principal_objects:
161 pull_step = self.pull_step_class()
162
163 pod = MagicMock()
164 pod.spec.service_account = self.principal.name
165
166 principal_objects.return_value = [self.principal]
167
168 principal = pull_step.get_principal_from_pod(pod, trust_domain=self.trust_domain)
169 self.assertEqual(principal, self.principal)
170
171 def test_get_principal_from_pod_noexist(self):
172 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client):
173 pull_step = self.pull_step_class()
174
175 pod = MagicMock()
176 pod.spec.service_account = "new-principal"
177
178 principal = pull_step.get_principal_from_pod(pod, trust_domain=self.trust_domain)
179 self.assertEqual(principal.name, "new-principal")
180 self.assertEqual(principal.trust_domain, self.trust_domain)
181
182 def test_get_image_from_pod_exists(self):
183 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client), \
184 patch.object(Image.objects, "get_items") as image_objects:
185 pull_step = self.pull_step_class()
186
187 container = MagicMock()
188 container.image = "%s:%s" % (self.image.name, self.image.tag)
189
190 pod = MagicMock()
191 pod.spec.containers = [container]
192
193 image_objects.return_value = [self.image]
194
195 image = pull_step.get_image_from_pod(pod)
196 self.assertEqual(image, self.image)
197
198 def test_get_image_from_pod_noexist(self):
199 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client):
200 pull_step = self.pull_step_class()
201
202 container = MagicMock()
203 container.image = "new-image:2.3" \
204
205 pod = MagicMock()
206 pod.spec.containers = [container]
207
208 image = pull_step.get_image_from_pod(pod)
209 self.assertEqual(image.name, "new-image")
210 self.assertEqual(image.tag, "2.3")
211 self.assertEqual(image.kind, "container")
212
213 def make_pod(self, name, trust_domain, principal, image):
214 container = MagicMock()
215 container.image = "%s:%s" % (image.name, image.tag)
216
217 pod = MagicMock()
218 pod.metadata.name = name
219 pod.metadata.namespace = trust_domain.name
220 pod.spec.service_account = principal.name
221
222 return pod
223
224 def test_pull_records_new_pod(self):
225 """ A pod is found in k8s that does not exist in XOS. A new KubernetesServiceInstance sohuld be created
226 """
227 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client), \
228 patch.object(self.pull_step_class, "get_trustdomain_from_pod") as get_trustdomain, \
229 patch.object(self.pull_step_class, "get_principal_from_pod") as get_principal, \
230 patch.object(self.pull_step_class, "get_slice_from_pod") as get_slice, \
231 patch.object(self.pull_step_class, "get_image_from_pod") as get_image, \
232 patch.object(KubernetesService.objects, "get_items") as service_objects, \
233 patch.object(KubernetesServiceInstance.objects, "get_items") as si_objects, \
234 patch.object(KubernetesServiceInstance, "save", autospec=True) as ksi_save:
235
236 service_objects.return_value = [self.service]
237
238 slice = Slice(name="myslice")
239
240 get_trustdomain.return_value = self.trust_domain
241 get_principal.return_value = self.principal
242 get_slice.return_value = slice
243 get_image.return_value = self.image
244
245 pod = self.make_pod("my-pod", self.trust_domain, self.principal, self.image)
246 pod.status.pod_ip = "1.2.3.4"
247
248 pull_step = self.pull_step_class()
249 pull_step.v1core.list_pod_for_all_namespaces.return_value = MagicMock(items=[pod])
250
251 pull_step.pull_records()
252
253 self.assertEqual(ksi_save.call_count, 1)
254 saved_ksi = ksi_save.call_args[0][0]
255
256 self.assertEqual(saved_ksi.name, "my-pod")
257 self.assertEqual(saved_ksi.pod_ip, "1.2.3.4")
258 self.assertEqual(saved_ksi.owner, self.service)
259 self.assertEqual(saved_ksi.slice, slice)
260 self.assertEqual(saved_ksi.image, self.image)
261 self.assertEqual(saved_ksi.xos_managed, False)
262
263 def test_pull_records_missing_pod(self):
264 """ A pod is found in k8s that does not exist in XOS. A new KubernetesServiceInstance sohuld be created
265 """
266 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client), \
267 patch.object(KubernetesService.objects, "get_items") as service_objects, \
268 patch.object(KubernetesServiceInstance.objects, "get_items") as si_objects, \
269 patch.object(KubernetesServiceInstance, "delete", autospec=True) as ksi_delete:
270 service_objects.return_value = [self.service]
271
272 si = KubernetesServiceInstance(name="my-pod", owner=self.service, xos_managed=False)
273 si_objects.return_value = [si]
274
275 pull_step = self.pull_step_class()
276 pull_step.v1core.list_pod_for_all_namespaces.return_value = MagicMock(items=[])
277
278 pull_step.pull_records()
279
280 self.assertEqual(ksi_delete.call_count, 1)
281 deleted_ksi = ksi_delete.call_args[0][0]
282
Scott Baker987748d2018-08-09 16:14:11 -0700283 def test_pull_records_new_pod_kafka_event(self):
284 """ A pod is found in k8s that does not exist in XOS. A new KubernetesServiceInstance sohuld be created
285 """
286 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client), \
287 patch.object(self.pull_step_class, "get_trustdomain_from_pod") as get_trustdomain, \
288 patch.object(self.pull_step_class, "get_principal_from_pod") as get_principal, \
289 patch.object(self.pull_step_class, "get_slice_from_pod") as get_slice, \
290 patch.object(self.pull_step_class, "get_image_from_pod") as get_image, \
291 patch.object(self.pull_step_class, "send_notification", autospec=True) as send_notification, \
292 patch.object(KubernetesService.objects, "get_items") as service_objects, \
293 patch.object(KubernetesServiceInstance.objects, "get_items") as si_objects, \
294 patch.object(KubernetesServiceInstance, "save", autospec=True) as ksi_save:
295
296 service_objects.return_value = [self.service]
297
298 slice = Slice(name="myslice")
299
300 get_trustdomain.return_value = self.trust_domain
301 get_principal.return_value = self.principal
302 get_slice.return_value = slice
303 get_image.return_value = self.image
304
305 pod = self.make_pod("my-pod", self.trust_domain, self.principal, self.image)
306 pod.status.pod_ip = "1.2.3.4"
307
308 pull_step = self.pull_step_class()
309 pull_step.kafka_producer = "foo"
310 pull_step.v1core.list_pod_for_all_namespaces.return_value = MagicMock(items=[pod])
311
312 pull_step.pull_records()
313
314 self.assertEqual(ksi_save.call_count, 2)
315
316 # Inspect the last KubernetesServiceInstance that was saved. There's no way to inspect the first one saved
317 # if there are multiple calls, as the sync step will cause the object to be updated.
318 saved_ksi = ksi_save.call_args[0][0]
319 self.assertEqual(saved_ksi.name, "my-pod")
320 self.assertEqual(saved_ksi.pod_ip, "1.2.3.4")
321 self.assertEqual(saved_ksi.owner, self.service)
322 self.assertEqual(saved_ksi.slice, slice)
323 self.assertEqual(saved_ksi.image, self.image)
324 self.assertEqual(saved_ksi.xos_managed, False)
325 self.assertEqual(saved_ksi.need_event, False)
326
327 self.assertEqual(send_notification.call_count, 1)
328 self.assertEqual(send_notification.call_args[0][1], saved_ksi)
329 self.assertEqual(send_notification.call_args[0][2], pod)
330 self.assertEqual(send_notification.call_args[0][3], "created")
331
332 def test_pull_records_existing_pod_kafka_event(self):
333 """ A pod is found in k8s that does not exist in XOS. A new KubernetesServiceInstance sohuld be created
334 """
335 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client), \
336 patch.object(self.pull_step_class, "get_trustdomain_from_pod") as get_trustdomain, \
337 patch.object(self.pull_step_class, "get_principal_from_pod") as get_principal, \
338 patch.object(self.pull_step_class, "get_slice_from_pod") as get_slice, \
339 patch.object(self.pull_step_class, "get_image_from_pod") as get_image, \
340 patch.object(self.pull_step_class, "send_notification", autospec=True) as send_notification, \
341 patch.object(KubernetesService.objects, "get_items") as service_objects, \
342 patch.object(KubernetesServiceInstance.objects, "get_items") as si_objects, \
343 patch.object(KubernetesServiceInstance, "save", autospec=True) as ksi_save:
344
345 service_objects.return_value = [self.service]
346
347 slice = Slice(name="myslice")
348
349 get_trustdomain.return_value = self.trust_domain
350 get_principal.return_value = self.principal
351 get_slice.return_value = slice
352 get_image.return_value = self.image
353
354 pod = self.make_pod("my-pod", self.trust_domain, self.principal, self.image)
355 pod.status.pod_ip = "1.2.3.4"
356
357 xos_pod = KubernetesServiceInstance(name="my-pod",
358 pod_ip="",
359 owner=self.service,
360 slice=slice,
361 image=self.image,
362 xos_managed=False,
363 need_event=False,
364 last_event_sent="created")
365 si_objects.return_value = [xos_pod]
366
367 pull_step = self.pull_step_class()
368 pull_step.kafka_producer = "foo"
369 pull_step.v1core.list_pod_for_all_namespaces.return_value = MagicMock(items=[pod])
370
371 pull_step.pull_records()
372
373 self.assertEqual(ksi_save.call_count, 2)
374
375 # Inspect the last KubernetesServiceInstance that was saved. There's no way to inspect the first one saved
376 # if there are multiple calls, as the sync step will cause the object to be updated.
377 saved_ksi = ksi_save.call_args[0][0]
378 self.assertEqual(saved_ksi.name, "my-pod")
379 self.assertEqual(saved_ksi.pod_ip, "1.2.3.4")
380 self.assertEqual(saved_ksi.owner, self.service)
381 self.assertEqual(saved_ksi.slice, slice)
382 self.assertEqual(saved_ksi.image, self.image)
383 self.assertEqual(saved_ksi.xos_managed, False)
384 self.assertEqual(saved_ksi.need_event, False)
385
386 self.assertEqual(send_notification.call_count, 1)
387 self.assertEqual(send_notification.call_args[0][1], saved_ksi)
388 self.assertEqual(send_notification.call_args[0][2], pod)
389 self.assertEqual(send_notification.call_args[0][3], "updated")
390
391 def test_send_notification_created(self):
392 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client):
393 pull_step = self.pull_step_class()
394 pull_step.kafka_producer = MagicMock()
395
396 pod = self.make_pod("my-pod", self.trust_domain, self.principal, self.image)
397 pod.status.pod_ip = "1.2.3.4"
398 pod.metadata.labels = {"foo": "bar"}
399 xos_pod = KubernetesServiceInstance(name="my-pod",
400 pod_ip="",
401 owner=self.service,
402 slice=slice,
403 image=self.image,
404 xos_managed=False,
405 need_event=False,
406 last_event_sent="created")
407 pull_step.send_notification(xos_pod, pod, "created")
408
409 self.assertEqual(pull_step.kafka_producer.send.call_count, 1)
410 topic = pull_step.kafka_producer.send.call_args[0][0]
411 event = json.loads(pull_step.kafka_producer.send.call_args[0][1])
412
413 self.assertEqual(topic, "xos.kubernetes.pod-details")
414
415 self.assertEqual(event["name"], "my-pod")
416 self.assertEqual(event["status"], "created")
Scott Baker3c2b8202018-08-15 10:51:55 -0700417 self.assertEqual(event["producer"], "k8s-sync")
Scott Baker987748d2018-08-09 16:14:11 -0700418 self.assertEqual(event["labels"], {"foo": "bar"})
419 self.assertEqual(event["netinterfaces"], [{"name": "primary", "addresses": ["1.2.3.4"]}])
420
421 def test_send_notification_deleted(self):
422 with patch.object(self.pull_step_class, "init_kubernetes_client", new=fake_init_kubernetes_client):
423 pull_step = self.pull_step_class()
424 pull_step.kafka_producer = MagicMock()
425
426 xos_pod = KubernetesServiceInstance(name="my-pod",
427 pod_ip="",
428 owner=self.service,
429 slice=slice,
430 image=self.image,
431 xos_managed=False,
432 need_event=False,
433 last_event_sent="created")
434 pull_step.send_notification(xos_pod, None, "deleted")
435
436 self.assertEqual(pull_step.kafka_producer.send.call_count, 1)
437 topic = pull_step.kafka_producer.send.call_args[0][0]
438 event = json.loads(pull_step.kafka_producer.send.call_args[0][1])
439
440 self.assertEqual(topic, "xos.kubernetes.pod-details")
441
442 self.assertEqual(event["name"], "my-pod")
443 self.assertEqual(event["status"], "deleted")
Scott Baker3c2b8202018-08-15 10:51:55 -0700444 self.assertEqual(event["producer"], "k8s-sync")
Scott Baker987748d2018-08-09 16:14:11 -0700445
Scott Baker13e953c2018-05-17 09:19:15 -0700446if __name__ == '__main__':
447 unittest.main()