Scott Baker | e27de6a | 2017-11-28 17:10:08 -0800 | [diff] [blame] | 1 | |
| 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 | |
| 16 | |
| 17 | import unittest |
| 18 | from mock import patch, call, Mock, MagicMock, PropertyMock |
| 19 | import mock |
| 20 | |
| 21 | import os, sys |
| 22 | |
| 23 | test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__))) |
| 24 | service_dir=os.path.join(test_path, "../../../..") |
| 25 | xos_dir=os.path.join(test_path, "../../..") |
| 26 | if not os.path.exists(os.path.join(test_path, "new_base")): |
| 27 | xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos") |
| 28 | services_dir=os.path.join(xos_dir, "../../xos_services") |
| 29 | |
Scott Baker | e96314e | 2018-01-07 08:28:46 -0800 | [diff] [blame] | 30 | # While transitioning from static to dynamic load, the path to find neighboring xproto files has changed. So check |
| 31 | # both possible locations... |
| 32 | def get_models_fn(service_name, xproto_name): |
| 33 | name = os.path.join(service_name, "xos", xproto_name) |
| 34 | if os.path.exists(os.path.join(services_dir, name)): |
| 35 | return name |
| 36 | else: |
| 37 | name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name) |
| 38 | if os.path.exists(os.path.join(services_dir, name)): |
| 39 | return name |
| 40 | raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name)) |
| 41 | |
Scott Baker | e27de6a | 2017-11-28 17:10:08 -0800 | [diff] [blame] | 42 | class TestSyncVSGServiceInstance(unittest.TestCase): |
| 43 | def setUp(self): |
| 44 | global SyncVSGServiceInstance, LeastLoadedNodeScheduler, MockObjectList |
| 45 | |
| 46 | self.sys_path_save = sys.path |
| 47 | sys.path.append(xos_dir) |
| 48 | sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base')) |
| 49 | |
| 50 | config = os.path.join(test_path, "test_config.yaml") |
| 51 | from xosconfig import Config |
| 52 | Config.clear() |
| 53 | Config.init(config, 'synchronizer-config-schema.yaml') |
| 54 | |
| 55 | from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor |
Scott Baker | e96314e | 2018-01-07 08:28:46 -0800 | [diff] [blame] | 56 | build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("vsg", "vsg.xproto"), |
| 57 | get_models_fn("addressmanager", "addressmanager.xproto")]) |
Scott Baker | e27de6a | 2017-11-28 17:10:08 -0800 | [diff] [blame] | 58 | |
| 59 | import synchronizers.new_base.modelaccessor |
| 60 | import synchronizers.new_base.model_policies.model_policy_tenantwithcontainer |
| 61 | import sync_vsgserviceinstance |
| 62 | from sync_vsgserviceinstance import SyncVSGServiceInstance, model_accessor |
| 63 | |
| 64 | from mock_modelaccessor import MockObjectList |
| 65 | |
| 66 | # import all class names to globals |
| 67 | for (k, v) in model_accessor.all_model_classes.items(): |
| 68 | globals()[k] = v |
| 69 | |
| 70 | # Some of the functions we call have side-effects. For example, creating a VSGServiceInstance may lead to creation of |
| 71 | # tags. Ideally, this wouldn't happen, but it does. So make sure we reset the world. |
| 72 | model_accessor.reset_all_object_stores() |
| 73 | |
| 74 | # attic functions that are not present in the mock model accessor |
| 75 | AddressManagerServiceInstance.set_attribute = Mock() |
| 76 | |
| 77 | self.syncstep = SyncVSGServiceInstance() |
| 78 | |
| 79 | # set up an object hierarchy that represents a Service and ServiceInstance |
| 80 | |
| 81 | self.user = User(email="testadmin@test.org") |
| 82 | self.service = VSGService(name="the_vsg_service", |
| 83 | id=1, |
| 84 | docker_image_name="reg/vsg_docker", |
| 85 | docker_insecure_registry=True, |
| 86 | dns_servers="dnsone,dnstwo", |
| 87 | url_filter_kind=None, |
| 88 | private_key_fn=os.path.join(test_path, "test_private_key")) |
| 89 | self.subscriber = MagicMock(firewall_rules = "rule1", |
| 90 | firewall_enable = True, |
| 91 | url_filter_enable = True, |
| 92 | url_filter_level="R", |
| 93 | cdn_enable=True, |
| 94 | uplink_speed=1234, |
| 95 | downlink_speed=5678, |
| 96 | enable_uverse=False, |
| 97 | status="suspended", |
| 98 | sync_attributes=["firewall_rules", "firewall_enable", "url_filter_enable", |
| 99 | "url_filter_level", "cdn_enable", "uplink_speed", |
| 100 | "downlink_speed", "enable_uverse", "status"]) |
| 101 | self.volt = MagicMock(s_tag=111, c_tag=222, subscriber=self.subscriber) |
Scott Baker | a6fa6ce | 2018-03-20 20:55:31 -0700 | [diff] [blame] | 102 | self.tenant = VSGServiceInstance(id=401, |
Scott Baker | e27de6a | 2017-11-28 17:10:08 -0800 | [diff] [blame] | 103 | volt=self.volt, |
| 104 | owner=self.service, |
| 105 | wan_container_ip="10.7.1.3", |
| 106 | wan_container_netbits="24", |
| 107 | wan_container_mac="02:42:0a:07:01:03", |
| 108 | wan_container_gateway_ip="10.7.1.1", |
| 109 | wan_vm_ip="10.7.1.2", |
| 110 | wan_vm_mac="02:42:0a:07:01:02", |
| 111 | sync_attributes = ["wan_container_ip", "wan_container_netbits", "wan_container_mac", |
| 112 | "wan_container_gateway_ip", "wan_vm_ip", "wan_vm_mac"]) |
| 113 | self.flavor = Flavor(name="m1.small") |
| 114 | self.npt_ctag = NetworkParameterType(name="c_tag", id=1) |
| 115 | self.npt_stag = NetworkParameterType(name="s_tag", id=2) |
| 116 | self.npt_neutron_port_name = NetworkParameterType(name="neutron_port_name", id=501) |
| 117 | self.priv_template = NetworkTemplate(name="access_network", visibility="private") |
| 118 | self.priv_network = Network(name="mysite_test1_private", template=self.priv_template) |
| 119 | self.image = Image(name="trusty-server-multi-nic") |
| 120 | self.deployment = Deployment(name="testdeployment") |
| 121 | self.user = User(email="smbaker", id=701) |
| 122 | self.controller = Controller(id=101) |
| 123 | self.node = Node(name="testnode") |
| 124 | self.slice = Slice(name="mysite_test1", default_flavor=self.flavor, default_isolation="vm", service=self.service, id=301) |
| 125 | self.instance = Instance(slice=self.slice, |
| 126 | instance_name="testinstance1_instance_name", |
| 127 | instance_id="testinstance1_instance_id", |
| 128 | name="testinstance1_name", |
| 129 | node=self.node, |
| 130 | creator=self.user, |
| 131 | controller=self.controller) |
| 132 | self.tenant.instance = self.instance |
| 133 | self.instance.get_ssh_ip = Mock(return_value="1.2.3.4") |
| 134 | self.controllerslice = ControllerSlice(slice_id=self.slice.id, controller_id=self.controller.id, id=201) |
| 135 | self.controlleruser = ControllerUser(user_id=self.user.id, controller_id=self.controller.id, id=601) |
| 136 | |
| 137 | def tearDown(self): |
| 138 | sys.path = self.sys_path_save |
| 139 | |
| 140 | def test_get_vsg_service(self): |
| 141 | with patch.object(VSGService.objects, "get_items") as vsgservice_objects: |
| 142 | vsgservice_objects.return_value = [self.service] |
| 143 | |
| 144 | self.tenant.owner = self.service |
| 145 | |
| 146 | self.assertEqual(self.syncstep.get_vsg_service(self.tenant), self.service) |
| 147 | |
| 148 | def test_get_extra_attributes(self): |
| 149 | with patch.object(VSGService.objects, "get_items") as vsgservice_objects: |
| 150 | vsgservice_objects.return_value = [self.service] |
| 151 | |
| 152 | attrs = self.syncstep.get_extra_attributes(self.tenant) |
| 153 | |
| 154 | desired_attrs = {"s_tags": [111], |
| 155 | "c_tags": [222], |
| 156 | "docker_remote_image_name": "reg/vsg_docker", |
| 157 | "docker_local_image_name": "reg/vsg_docker", |
| 158 | "docker_opts": "--insecure-registry reg", |
| 159 | "dnsdemux_ip": "none", |
| 160 | "cdn_prefixes": [], |
| 161 | "full_setup": True, |
| 162 | "isolation": "vm", |
| 163 | "safe_browsing_macs": [], |
| 164 | "container_name": "vsg-111-222", |
| 165 | "dns_servers": ["dnsone", "dnstwo"], |
| 166 | "url_filter_kind": None, |
| 167 | |
| 168 | "firewall_rules": "rule1", |
| 169 | "firewall_enable": True, |
| 170 | "url_filter_enable": True, |
| 171 | "url_filter_level": "R", |
| 172 | "cdn_enable": True, |
| 173 | "uplink_speed": 1234, |
| 174 | "downlink_speed": 5678, |
| 175 | "enable_uverse": False, |
| 176 | "status": "suspended"} |
| 177 | |
| 178 | self.assertDictContainsSubset(desired_attrs, attrs) |
| 179 | |
| 180 | |
| 181 | def test_sync_record(self): |
| 182 | with patch.object(VSGService.objects, "get_items") as vsgservice_objects, \ |
| 183 | patch.object(Slice.objects, "get_items") as slice_objects, \ |
| 184 | patch.object(User.objects, "get_items") as user_objects, \ |
| 185 | patch.object(ControllerSlice.objects, "get_items") as controllerslice_objects, \ |
| 186 | patch.object(ControllerUser.objects, "get_items") as controlleruser_objects, \ |
| 187 | patch.object(SyncVSGServiceInstance, "run_playbook") as run_playbook: |
| 188 | slice_objects.return_value = [self.slice] |
| 189 | vsgservice_objects.return_value = [self.service] |
| 190 | controllerslice_objects.return_value = [self.controllerslice] |
| 191 | controlleruser_objects.return_value = [self.controlleruser] |
| 192 | user_objects.return_value = [self.user] |
| 193 | |
| 194 | self.tenant.updated = 10 |
| 195 | self.tenant.policed = 20 |
| 196 | self.tenant.enacted = None |
| 197 | |
| 198 | run_playbook.return_value = True |
| 199 | |
| 200 | self.syncstep.sync_record(self.tenant) |
| 201 | |
| 202 | run_playbook.assert_called() |
| 203 | |
| 204 | attrs = run_playbook.call_args[0][1] |
| 205 | |
| 206 | desired_attrs = {"username": "ubuntu", |
| 207 | "ansible_tag": "VSGServiceInstance_401", |
| 208 | "instance_name": "testinstance1_name", |
| 209 | "hostname": "testnode", |
| 210 | "private_key": "some_key\n", |
| 211 | "ssh_ip": "1.2.3.4", |
| 212 | "instance_id": "testinstance1_instance_id", |
| 213 | |
| 214 | "wan_container_ip": "10.7.1.3", |
| 215 | "wan_container_netbits": "24", |
| 216 | "wan_container_mac": "02:42:0a:07:01:03", |
| 217 | "wan_container_gateway_ip": "10.7.1.1", |
| 218 | "wan_vm_ip": "10.7.1.2", |
| 219 | "wan_vm_mac": "02:42:0a:07:01:02", |
| 220 | |
| 221 | "s_tags": [111], |
| 222 | "c_tags": [222], |
| 223 | "docker_remote_image_name": "reg/vsg_docker", |
| 224 | "docker_local_image_name": "reg/vsg_docker", |
| 225 | "docker_opts": "--insecure-registry reg", |
| 226 | "dnsdemux_ip": "none", |
| 227 | "cdn_prefixes": [], |
| 228 | "full_setup": True, |
| 229 | "isolation": "vm", |
| 230 | "safe_browsing_macs": [], |
| 231 | "container_name": "vsg-111-222", |
| 232 | "dns_servers": ["dnsone", "dnstwo"], |
| 233 | "url_filter_kind": None, |
| 234 | |
| 235 | "firewall_rules": "rule1", |
| 236 | "firewall_enable": True, |
| 237 | "url_filter_enable": True, |
| 238 | "url_filter_level": "R", |
| 239 | "cdn_enable": True, |
| 240 | "uplink_speed": 1234, |
| 241 | "downlink_speed": 5678, |
| 242 | "enable_uverse": False, |
| 243 | "status": "suspended"} |
| 244 | |
| 245 | self.assertDictContainsSubset(desired_attrs, attrs) |
| 246 | |
| 247 | def test_sync_record_emptysubscriber(self): |
| 248 | with patch.object(VSGService.objects, "get_items") as vsgservice_objects, \ |
| 249 | patch.object(Slice.objects, "get_items") as slice_objects, \ |
| 250 | patch.object(User.objects, "get_items") as user_objects, \ |
| 251 | patch.object(ControllerSlice.objects, "get_items") as controllerslice_objects, \ |
| 252 | patch.object(ControllerUser.objects, "get_items") as controlleruser_objects, \ |
| 253 | patch.object(SyncVSGServiceInstance, "run_playbook") as run_playbook: |
| 254 | slice_objects.return_value = [self.slice] |
| 255 | vsgservice_objects.return_value = [self.service] |
| 256 | controllerslice_objects.return_value = [self.controllerslice] |
| 257 | controlleruser_objects.return_value = [self.controlleruser] |
| 258 | user_objects.return_value = [self.user] |
| 259 | |
| 260 | self.tenant.updated = 10 |
| 261 | self.tenant.policed = 20 |
| 262 | self.tenant.enacted = None |
| 263 | |
| 264 | self.volt.subscriber = MagicMock() |
| 265 | |
| 266 | run_playbook.return_value = True |
| 267 | |
| 268 | self.syncstep.sync_record(self.tenant) |
| 269 | |
| 270 | run_playbook.assert_called() |
| 271 | |
| 272 | attrs = run_playbook.call_args[0][1] |
| 273 | |
| 274 | desired_attrs = {"firewall_rules": "", |
| 275 | "firewall_enable": False, |
| 276 | "url_filter_enable": False, |
| 277 | "url_filter_level": "PG", |
| 278 | "cdn_enable": False, |
| 279 | "uplink_speed": 1000000000, |
| 280 | "downlink_speed": 1000000000, |
| 281 | "enable_uverse": True, |
| 282 | "status": "enabled"} |
| 283 | |
| 284 | self.assertDictContainsSubset(desired_attrs, attrs) |
| 285 | |
| 286 | def test_sync_record_no_policy(self): |
| 287 | with patch.object(SyncVSGServiceInstance, "run_playbook") as run_playbook: |
| 288 | |
| 289 | self.tenant.updated = 10 |
| 290 | self.tenant.policed = 5 # policies need to be run |
| 291 | self.tenant.enacted = None |
| 292 | |
| 293 | with self.assertRaises(Exception) as e: |
| 294 | self.syncstep.sync_record(self.tenant) |
| 295 | self.assertIn("due to waiting on model policy", e.exception.message) |
| 296 | |
| 297 | run_playbook.assert_not_called() |
| 298 | |
| 299 | def test_sync_record_instance_not_ready(self): |
| 300 | with patch.object(SyncVSGServiceInstance, "run_playbook") as run_playbook: |
| 301 | |
| 302 | self.tenant.updated = 10 |
| 303 | self.tenant.policed = 20 |
| 304 | self.tenant.enacted = None |
| 305 | |
| 306 | self.instance.instance_name = None # no instance_name means instance is not ready |
| 307 | |
| 308 | with self.assertRaises(Exception) as e: |
| 309 | self.syncstep.sync_record(self.tenant) |
| 310 | self.assertIn("due to waiting on instance.instance_name", e.exception.message) |
| 311 | |
| 312 | run_playbook.assert_not_called() |
| 313 | |
| 314 | def test_delete_record_no_policy(self): |
| 315 | self.tenant.updated = 10 |
| 316 | self.tenant.policed = 20 |
| 317 | self.tenant.enacted = None |
| 318 | |
| 319 | self.syncstep.delete_record(self.tenant) |
| 320 | |
| 321 | # delete doesn't actually do anything, so nothing to further test. |
| 322 | |
| 323 | def test_delete_record_no_policy(self): |
| 324 | self.tenant.updated = 10 |
| 325 | self.tenant.policed = 5 # policies need to be run |
| 326 | self.tenant.enacted = None |
| 327 | |
| 328 | with self.assertRaises(Exception) as e: |
| 329 | self.syncstep.delete_record(self.tenant) |
| 330 | self.assertIn("due to waiting on model policy", e.exception.message) |
| 331 | |
| 332 | if __name__ == '__main__': |
| 333 | unittest.main() |
| 334 | |
| 335 | |