blob: 6bd1cfc26b87dd91207a092e4fb863ac419564d9 [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
102 b = xossynchronizer.backend.Backend()
103 steps_dir = Config.get("steps_dir")
104 self.steps = b.load_sync_step_modules(steps_dir)
105 self.synchronizer = xossynchronizer.event_loop.XOSObserver(self.steps)
106
107 def tearDown(self):
108 sys.path = self.sys_path_save
109 os.chdir(self.cwd_save)
110
111 @mock.patch(
112 "steps.sync_instances.syncstep.run_template",
113 side_effect=run_fake_ansible_template,
114 )
115 @mock.patch("xossynchronizer.event_loop.model_accessor")
116 def test_delete_record(self, mock_run_template, mock_modelaccessor):
117 with mock.patch.object(Instance, "save") as instance_save:
118 o = Instance()
119 o.name = "Sisi Pascal"
120
121 o.synchronizer_step = steps.sync_instances.SyncInstances()
122 self.synchronizer.delete_record(o, log)
123
124 a = get_ansible_output()
125 self.assertDictContainsSubset({"delete": True, "name": o.name}, a)
126 o.save.assert_called_with(update_fields=["backend_need_reap"])
127
128 @mock.patch(
129 "steps.sync_instances.syncstep.run_template",
130 side_effect=run_fake_ansible_template_fail,
131 )
132 @mock.patch("xossynchronizer.event_loop.model_accessor")
133 def test_delete_record_fail(self, mock_run_template, mock_modelaccessor):
134 with mock.patch.object(Instance, "save") as instance_save:
135 o = Instance()
136 o.name = "Sisi Pascal"
137
138 o.synchronizer_step = steps.sync_instances.SyncInstances()
139
140 with self.assertRaises(Exception) as e:
141 self.synchronizer.delete_record(o, log)
142
143 self.assertEqual(
144 e.exception.message, "Nonzero rc from Ansible during delete_record"
145 )
146
147 @mock.patch(
148 "steps.sync_instances.syncstep.run_template",
149 side_effect=run_fake_ansible_template,
150 )
151 @mock.patch("xossynchronizer.event_loop.model_accessor")
152 def test_sync_record(self, mock_run_template, mock_modelaccessor):
153 with mock.patch.object(Instance, "save") as instance_save:
154 o = Instance()
155 o.name = "Sisi Pascal"
156
157 o.synchronizer_step = steps.sync_instances.SyncInstances()
158 self.synchronizer.sync_record(o, log)
159
160 a = get_ansible_output()
161 self.assertDictContainsSubset({"delete": False, "name": o.name}, a)
162 o.save.assert_called_with(
163 update_fields=[
164 "enacted",
165 "backend_status",
166 "backend_register",
167 "backend_code",
168 ]
169 )
170
171 @mock.patch(
172 "steps.sync_instances.syncstep.run_template",
173 side_effect=run_fake_ansible_template,
174 )
175 @mock.patch("xossynchronizer.event_loop.model_accessor")
176 def test_sync_cohort(self, mock_run_template, mock_modelaccessor):
177 with mock.patch.object(Instance, "save") as instance_save, mock.patch.object(
178 ControllerSlice, "save"
179 ) as controllerslice_save:
180 cs = ControllerSlice()
181 s = Slice(name="SP SP")
182 cs.slice = s
183
184 o = Instance()
185 o.name = "Sisi Pascal"
186 o.slice = s
187
188 cohort = [cs, o]
189 o.synchronizer_step = steps.sync_instances.SyncInstances()
190 cs.synchronizer_step = steps.sync_controller_slices.SyncControllerSlices()
191
192 self.synchronizer.sync_cohort(cohort, False)
193
194 a = get_ansible_output()
195 self.assertDictContainsSubset({"delete": False, "name": o.name}, a)
196 o.save.assert_called_with(
197 update_fields=[
198 "enacted",
199 "backend_status",
200 "backend_register",
201 "backend_code",
202 ]
203 )
204 cs.save.assert_called_with(
205 update_fields=[
206 "enacted",
207 "backend_status",
208 "backend_register",
209 "backend_code",
210 ]
211 )
212
213 @mock.patch(
214 "steps.sync_instances.syncstep.run_template",
215 side_effect=run_fake_ansible_template,
216 )
217 @mock.patch("xossynchronizer.event_loop.model_accessor")
218 def test_deferred_exception(self, mock_run_template, mock_modelaccessor):
219 with mock.patch.object(Instance, "save") as instance_save:
220 cs = ControllerSlice()
221 s = Slice(name="SP SP")
222 cs.slice = s
223 cs.force_defer = True
224
225 o = Instance()
226 o.name = "Sisi Pascal"
227 o.slice = s
228
229 cohort = [cs, o]
230 o.synchronizer_step = steps.sync_instances.SyncInstances()
231 cs.synchronizer_step = steps.sync_controller_slices.SyncControllerSlices()
232
233 self.synchronizer.sync_cohort(cohort, False)
234 o.save.assert_called_with(
235 always_update_timestamp=True,
236 update_fields=["backend_status", "backend_register"],
237 )
238 self.assertEqual(cs.backend_code, 0)
239
240 self.assertIn("Force", cs.backend_status)
241 self.assertIn("Failed due to", o.backend_status)
242
243 @mock.patch(
244 "steps.sync_instances.syncstep.run_template",
245 side_effect=run_fake_ansible_template,
246 )
247 @mock.patch("xossynchronizer.event_loop.model_accessor")
248 def test_backend_status(self, mock_run_template, mock_modelaccessor):
249 with mock.patch.object(Instance, "save") as instance_save:
250 cs = ControllerSlice()
251 s = Slice(name="SP SP")
252 cs.slice = s
253 cs.force_fail = True
254
255 o = Instance()
256 o.name = "Sisi Pascal"
257 o.slice = s
258
259 cohort = [cs, o]
260 o.synchronizer_step = steps.sync_instances.SyncInstances()
261 cs.synchronizer_step = steps.sync_controller_slices.SyncControllerSlices()
262
263 self.synchronizer.sync_cohort(cohort, False)
264 o.save.assert_called_with(
265 always_update_timestamp=True,
266 update_fields=["backend_status", "backend_register"],
267 )
268 self.assertIn("Force", cs.backend_status)
269 self.assertIn("Failed due to", o.backend_status)
270
271 @mock.patch(
272 "steps.sync_instances.syncstep.run_template",
273 side_effect=run_fake_ansible_template,
274 )
275 @mock.patch("xossynchronizer.event_loop.model_accessor")
276 def test_fetch_pending(self, mock_run_template, mock_accessor, *_other_accessors):
277 pending_objects, pending_steps = self.synchronizer.fetch_pending()
278 pending_objects2 = list(pending_objects)
279
280 any_cs = next(
281 obj for obj in pending_objects if obj.leaf_model_name == "ControllerSlice"
282 )
283 any_instance = next(
284 obj for obj in pending_objects2 if obj.leaf_model_name == "Instance"
285 )
286
287 slice = Slice()
288 any_instance.slice = slice
289 any_cs.slice = slice
290
291 self.synchronizer.external_dependencies = []
292 cohorts = self.synchronizer.compute_dependent_cohorts(pending_objects, False)
293 flat_objects = [item for cohort in cohorts for item in cohort]
294
295 self.assertEqual(set(flat_objects), set(pending_objects))
296
297 @mock.patch(
298 "steps.sync_instances.syncstep.run_template",
299 side_effect=run_fake_ansible_template,
300 )
301 @mock.patch("xossynchronizer.event_loop.model_accessor")
302 def test_fetch_pending_with_external_dependencies(
303 self, mock_run_template, mock_accessor, *_other_accessors
304 ):
305 pending_objects, pending_steps = self.synchronizer.fetch_pending()
306 pending_objects2 = list(pending_objects)
307
308 any_cn = next(
309 obj for obj in pending_objects if obj.leaf_model_name == "ControllerNetwork"
310 )
311 any_user = next(
312 obj for obj in pending_objects2 if obj.leaf_model_name == "User"
313 )
314
315 cohorts = self.synchronizer.compute_dependent_cohorts(pending_objects, False)
316
317 flat_objects = [item for cohort in cohorts for item in cohort]
318 self.assertEqual(set(flat_objects), set(pending_objects))
319
320 # These cannot be None, but for documentation purposes
321 self.assertIsNotNone(any_cn)
322 self.assertIsNotNone(any_user)
323
324 @mock.patch(
325 "steps.sync_instances.syncstep.run_template",
326 side_effect=run_fake_ansible_template,
327 )
328 @mock.patch("xossynchronizer.event_loop.model_accessor")
329 def test_external_dependency_exception(self, mock_run_template, mock_modelaccessor):
330 cs = ControllerSlice()
331 s = Slice(name="SP SP")
332 cs.slice = s
333
334 o = Instance()
335 o.name = "Sisi Pascal"
336 o.slice = s
337
338 cohort = [cs, o]
339 o.synchronizer_step = None
340 o.synchronizer_step = steps.sync_instances.SyncInstances()
341
342 self.synchronizer.sync_cohort(cohort, False)
343
344
345if __name__ == "__main__":
346 unittest.main()