blob: cfba52d71f4bcace4097d2c5d04313334d032748 [file] [log] [blame]
Scott Bakerbba67b62019-01-28 17:38:21 -08001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import json
16import unittest
17from mock import patch
18import mock
19import pdb
20import networkx as nx
21
22import os
23import sys
24
25test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
26sync_lib_dir = os.path.join(test_path, "..", "xossynchronizer")
27xos_dir = os.path.join(test_path, "..", "..", "..", "xos")
28
29ANSIBLE_FILE = "/tmp/payload_test"
30
31log = None
32
33
34def run_fake_ansible_template(*args, **kwargs):
35 opts = args[1]
36 open(ANSIBLE_FILE, "w").write(json.dumps(opts))
37 return [{"rc": 0}]
38
39
40def run_fake_ansible_template_fail(*args, **kwargs):
41 opts = args[1]
42 open(ANSIBLE_FILE, "w").write(json.dumps(opts))
43 return [{"rc": 1}]
44
45
46def get_ansible_output():
47 ansible_str = open(ANSIBLE_FILE).read()
48 return json.loads(ansible_str)
49
50
51class TestPayload(unittest.TestCase):
52 @classmethod
53 def setUpClass(cls):
54
55 global log
56
57 config = os.path.join(test_path, "test_config.yaml")
58 from xosconfig import Config
59
60 Config.clear()
61 Config.init(config, "synchronizer-config-schema.yaml")
62
63 if not log:
64 from multistructlog import create_logger
65
66 log = create_logger(Config().get("logging"))
67
68 def setUp(self):
69
70 global log, steps, event_loop
71
72 self.sys_path_save = sys.path
73 self.cwd_save = os.getcwd()
74
75 config = os.path.join(test_path, "test_config.yaml")
76 from xosconfig import Config
77
78 Config.clear()
79 Config.init(config, "synchronizer-config-schema.yaml")
80
81 from xossynchronizer.mock_modelaccessor_build import (
82 build_mock_modelaccessor,
83 )
84
85 build_mock_modelaccessor(sync_lib_dir, xos_dir, services_dir=None, service_xprotos=[])
86
87 os.chdir(os.path.join(test_path, "..")) # config references tests/model-deps
88
89 import xossynchronizer.event_loop
90
91 reload(xossynchronizer.event_loop)
92 import xossynchronizer.backend
93
94 reload(xossynchronizer.backend)
95 import steps.sync_instances
96 import steps.sync_controller_slices
97 from xossynchronizer.modelaccessor import model_accessor
98
99 # import all class names to globals
100 for (k, v) in model_accessor.all_model_classes.items():
101 globals()[k] = v
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800102 b = xossynchronizer.backend.Backend(model_accessor = model_accessor)
Scott Bakerbba67b62019-01-28 17:38:21 -0800103 steps_dir = Config.get("steps_dir")
104 self.steps = b.load_sync_step_modules(steps_dir)
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800105 self.synchronizer = xossynchronizer.event_loop.XOSObserver(self.steps, model_accessor)
Scott Bakerbba67b62019-01-28 17:38:21 -0800106
107 def tearDown(self):
108 sys.path = self.sys_path_save
109 os.chdir(self.cwd_save)
110
111 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800112 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800113 side_effect=run_fake_ansible_template,
114 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800115 def test_delete_record(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800116 with mock.patch.object(Instance, "save") as instance_save:
117 o = Instance()
118 o.name = "Sisi Pascal"
119
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800120 o.synchronizer_step = steps.sync_instances.SyncInstances(model_accessor = self.synchronizer.model_accessor)
Scott Bakerbba67b62019-01-28 17:38:21 -0800121 self.synchronizer.delete_record(o, log)
122
123 a = get_ansible_output()
124 self.assertDictContainsSubset({"delete": True, "name": o.name}, a)
125 o.save.assert_called_with(update_fields=["backend_need_reap"])
126
127 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800128 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800129 side_effect=run_fake_ansible_template_fail,
130 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800131 def test_delete_record_fail(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800132 with mock.patch.object(Instance, "save") as instance_save:
133 o = Instance()
134 o.name = "Sisi Pascal"
135
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800136 o.synchronizer_step = steps.sync_instances.SyncInstances(model_accessor = self.synchronizer.model_accessor)
Scott Bakerbba67b62019-01-28 17:38:21 -0800137
138 with self.assertRaises(Exception) as e:
139 self.synchronizer.delete_record(o, log)
140
141 self.assertEqual(
142 e.exception.message, "Nonzero rc from Ansible during delete_record"
143 )
144
145 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800146 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800147 side_effect=run_fake_ansible_template,
148 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800149 def test_sync_record(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800150 with mock.patch.object(Instance, "save") as instance_save:
151 o = Instance()
152 o.name = "Sisi Pascal"
153
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800154 o.synchronizer_step = steps.sync_instances.SyncInstances(model_accessor = self.synchronizer.model_accessor)
Scott Bakerbba67b62019-01-28 17:38:21 -0800155 self.synchronizer.sync_record(o, log)
156
157 a = get_ansible_output()
158 self.assertDictContainsSubset({"delete": False, "name": o.name}, a)
159 o.save.assert_called_with(
160 update_fields=[
161 "enacted",
162 "backend_status",
163 "backend_register",
164 "backend_code",
165 ]
166 )
167
168 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800169 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800170 side_effect=run_fake_ansible_template,
171 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800172 def test_sync_cohort(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800173 with mock.patch.object(Instance, "save") as instance_save, mock.patch.object(
174 ControllerSlice, "save"
175 ) as controllerslice_save:
176 cs = ControllerSlice()
177 s = Slice(name="SP SP")
178 cs.slice = s
179
180 o = Instance()
181 o.name = "Sisi Pascal"
182 o.slice = s
183
184 cohort = [cs, o]
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800185 o.synchronizer_step = steps.sync_instances.SyncInstances(model_accessor = self.synchronizer.model_accessor)
186 cs.synchronizer_step = steps.sync_controller_slices.SyncControllerSlices(
187 model_accessor = self.synchronizer.model_accessor
188 )
Scott Bakerbba67b62019-01-28 17:38:21 -0800189
190 self.synchronizer.sync_cohort(cohort, False)
191
192 a = get_ansible_output()
193 self.assertDictContainsSubset({"delete": False, "name": o.name}, a)
194 o.save.assert_called_with(
195 update_fields=[
196 "enacted",
197 "backend_status",
198 "backend_register",
199 "backend_code",
200 ]
201 )
202 cs.save.assert_called_with(
203 update_fields=[
204 "enacted",
205 "backend_status",
206 "backend_register",
207 "backend_code",
208 ]
209 )
210
211 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800212 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800213 side_effect=run_fake_ansible_template,
214 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800215 def test_deferred_exception(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800216 with mock.patch.object(Instance, "save") as instance_save:
217 cs = ControllerSlice()
218 s = Slice(name="SP SP")
219 cs.slice = s
220 cs.force_defer = True
221
222 o = Instance()
223 o.name = "Sisi Pascal"
224 o.slice = s
225
226 cohort = [cs, o]
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800227 o.synchronizer_step = steps.sync_instances.SyncInstances(model_accessor=self.synchronizer.model_accessor)
228 cs.synchronizer_step = steps.sync_controller_slices.SyncControllerSlices(
229 model_accessor=self.synchronizer.model_accessor
230 )
Scott Bakerbba67b62019-01-28 17:38:21 -0800231
232 self.synchronizer.sync_cohort(cohort, False)
233 o.save.assert_called_with(
234 always_update_timestamp=True,
235 update_fields=["backend_status", "backend_register"],
236 )
237 self.assertEqual(cs.backend_code, 0)
238
239 self.assertIn("Force", cs.backend_status)
240 self.assertIn("Failed due to", o.backend_status)
241
242 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800243 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800244 side_effect=run_fake_ansible_template,
245 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800246 def test_backend_status(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800247 with mock.patch.object(Instance, "save") as instance_save:
248 cs = ControllerSlice()
249 s = Slice(name="SP SP")
250 cs.slice = s
251 cs.force_fail = True
252
253 o = Instance()
254 o.name = "Sisi Pascal"
255 o.slice = s
256
257 cohort = [cs, o]
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800258 o.synchronizer_step = steps.sync_instances.SyncInstances(model_accessor=self.synchronizer.model_accessor)
259 cs.synchronizer_step = steps.sync_controller_slices.SyncControllerSlices(
260 model_accessor=self.synchronizer.model_accessor)
Scott Bakerbba67b62019-01-28 17:38:21 -0800261
262 self.synchronizer.sync_cohort(cohort, False)
263 o.save.assert_called_with(
264 always_update_timestamp=True,
265 update_fields=["backend_status", "backend_register"],
266 )
267 self.assertIn("Force", cs.backend_status)
268 self.assertIn("Failed due to", o.backend_status)
269
270 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800271 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800272 side_effect=run_fake_ansible_template,
273 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800274 def test_fetch_pending(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800275 pending_objects, pending_steps = self.synchronizer.fetch_pending()
276 pending_objects2 = list(pending_objects)
277
278 any_cs = next(
279 obj for obj in pending_objects if obj.leaf_model_name == "ControllerSlice"
280 )
281 any_instance = next(
282 obj for obj in pending_objects2 if obj.leaf_model_name == "Instance"
283 )
284
285 slice = Slice()
286 any_instance.slice = slice
287 any_cs.slice = slice
288
289 self.synchronizer.external_dependencies = []
290 cohorts = self.synchronizer.compute_dependent_cohorts(pending_objects, False)
291 flat_objects = [item for cohort in cohorts for item in cohort]
292
293 self.assertEqual(set(flat_objects), set(pending_objects))
294
295 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800296 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800297 side_effect=run_fake_ansible_template,
298 )
Scott Bakerbba67b62019-01-28 17:38:21 -0800299 def test_fetch_pending_with_external_dependencies(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800300 self, mock_run_template,
Scott Bakerbba67b62019-01-28 17:38:21 -0800301 ):
302 pending_objects, pending_steps = self.synchronizer.fetch_pending()
303 pending_objects2 = list(pending_objects)
304
305 any_cn = next(
306 obj for obj in pending_objects if obj.leaf_model_name == "ControllerNetwork"
307 )
308 any_user = next(
309 obj for obj in pending_objects2 if obj.leaf_model_name == "User"
310 )
311
312 cohorts = self.synchronizer.compute_dependent_cohorts(pending_objects, False)
313
314 flat_objects = [item for cohort in cohorts for item in cohort]
315 self.assertEqual(set(flat_objects), set(pending_objects))
316
317 # These cannot be None, but for documentation purposes
318 self.assertIsNotNone(any_cn)
319 self.assertIsNotNone(any_user)
320
321 @mock.patch(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800322 "steps.sync_instances.ansiblesyncstep.run_template",
Scott Bakerbba67b62019-01-28 17:38:21 -0800323 side_effect=run_fake_ansible_template,
324 )
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800325 def test_external_dependency_exception(self, mock_run_template):
Scott Bakerbba67b62019-01-28 17:38:21 -0800326 cs = ControllerSlice()
327 s = Slice(name="SP SP")
328 cs.slice = s
329
330 o = Instance()
331 o.name = "Sisi Pascal"
332 o.slice = s
333
334 cohort = [cs, o]
335 o.synchronizer_step = None
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800336 o.synchronizer_step = steps.sync_instances.SyncInstances(model_accessor=self.synchronizer.model_accessor)
Scott Bakerbba67b62019-01-28 17:38:21 -0800337
338 self.synchronizer.sync_cohort(cohort, False)
339
340
341if __name__ == "__main__":
342 unittest.main()