[SEBA-450] (part 2)
Add tox testing support on additional XOS library modules:
- xos-api
- xos-kafka (has no tests)
- xos-migrate (has no tests)
- xos-synchronizer
Change-Id: I98195bc9747971d3515882d517affe058dd86ac5
diff --git a/lib/xos-api/tests/fake_stub.py b/lib/xos-api/tests/fake_stub.py
new file mode 100644
index 0000000..81c3988
--- /dev/null
+++ b/lib/xos-api/tests/fake_stub.py
@@ -0,0 +1,595 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" fake_stub.py
+
+ Implements a simple fake grpc stub to use for unit testing.
+"""
+
+from __future__ import absolute_import
+
+import functools
+
+ContentTypeMap = {}
+
+
+class FakeObj(object):
+ BASES = []
+
+ def __init__(self, fields=[], **kwargs):
+ super(FakeObj, self).__setattr__("is_set", {})
+ super(FakeObj, self).__setattr__("fields", [])
+
+ for f in fields:
+ name = f["name"]
+ self.fields.append(name)
+ setattr(self, name, f["default"])
+
+ super(FakeObj, self).__setattr__("is_set", {})
+ for (k, v) in kwargs.items():
+ setattr(self, k, v)
+
+ def __repr__(self):
+ lines = []
+ for k in self.fields:
+ if self.is_set.get(k, False):
+ lines.append('%s: "%s"' % (k, getattr(self, k)))
+ if lines:
+ return "\n".join(lines) + "\n"
+ else:
+ return ""
+
+ def __setattr__(self, name, value):
+ self.is_set[name] = True
+ super(FakeObj, self).__setattr__(name, value)
+
+ def HasField(self, name):
+ """ Return True if the field is set in the protobuf. """
+
+ # gRPC throws a valueerror if the field doesn't exist in the schema
+ if name not in self.fields:
+ raise ValueError("Field %s does not exist in schema" % name)
+
+ # Fields that are always set
+ if name in ["leaf_model_name"]:
+ return True
+
+ field = self.DESCRIPTOR.fields_by_name[name].field_decl
+
+ # Reverse foreign keys lists are never None, they are an empty list
+ if field.get("fk_reverse", None):
+ return True
+
+ return self.is_set.get(name, False)
+
+ def ListFields(self):
+ fbn = self.DESCRIPTOR.fields_by_name
+ fieldlist = []
+ for (k, v) in fbn.items():
+ if self.is_set.get(k, False):
+ fieldlist.append((v, getattr(self, k)))
+ return fieldlist
+
+ @property
+ def self_content_type_id(self):
+ return "xos.%s" % self.__class__.__name__.lower()
+
+
+class FakeExtensionManager(object):
+ def __init__(self, obj, extensions):
+ self.obj = obj
+ self.extensions = extensions
+
+ def _FindExtensionByName(self, name):
+ return name
+
+ def __getitem__(self, name, default=None):
+ if name in self.extensions:
+ return self.extensions[name]
+ return default
+
+
+class FakeFieldOption(object):
+ def __init__(self, modelName=None, reverseFieldName=None):
+ self.modelName = modelName
+ self.reverseFieldName = reverseFieldName
+
+
+class FakeField(object):
+ def __init__(self, field):
+ extensions = {}
+
+ self.field_decl = field
+ self.name = field["name"]
+
+ fk_model = field.get("fk_model", None)
+ if fk_model:
+ reverseFieldName = field.get("fk_reverseFieldName", None)
+ extensions["xos.foreignKey"] = FakeFieldOption(
+ modelName=fk_model, reverseFieldName=reverseFieldName
+ )
+
+ fk_reverse = field.get("fk_reverse", None)
+ if fk_reverse:
+ extensions["xos.reverseForeignKey"] = FakeFieldOption(modelName=fk_reverse)
+
+ self.Extensions = FakeExtensionManager(self, extensions)
+
+ def GetOptions(self):
+ return self
+
+
+class FakeDescriptor(object):
+ def __init__(self, objName):
+ global ContentTypeIdCounter
+ global ContentTypeMap
+ self.objName = objName
+ if objName in ContentTypeMap:
+ ct = ContentTypeMap[objName]
+ else:
+ ct = "xos.%s" % objName.lower()
+ ContentTypeMap[objName] = ct
+ self.Extensions = FakeExtensionManager(self, {"xos.contentTypeId": ct})
+
+ def GetOptions(self):
+ return self
+
+ @property
+ def fields_by_name(self):
+ cls = globals()[self.objName]
+ fbn = {}
+ for field in cls.FIELDS:
+ fake_field = FakeField(field)
+ fbn[field["name"]] = fake_field
+
+ return fbn
+
+
+class Controller(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {"name": "deployment_id", "default": 0, "fk_model": "Deployment"},
+ {"name": "class_names", "default": "Controller"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(Controller, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("Controller")
+
+
+class Deployment(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {"name": "class_names", "default": "Deployment"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(Deployment, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("Controller")
+
+
+class User(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "email", "default": ""},
+ {"name": "site_id", "default": 0, "fk_model": "Site"},
+ {"name": "class_names", "default": "User"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(User, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("User")
+
+
+class Slice(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {
+ "name": "site_id",
+ "default": 0,
+ "fk_model": "Site",
+ "fk_reverseFieldName": "slices",
+ },
+ {"name": "service_id", "default": 0, "fk_model": "Service"},
+ {"name": "creator_id", "default": 0, "fk_model": "User"},
+ {"name": "networks_ids", "default": [], "fk_reverse": "Network"},
+ {"name": "network", "default": ""},
+ {"name": "leaf_model_name", "default": "Slice"},
+ {"name": "class_names", "default": "Slice"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(Slice, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("Slice")
+
+
+class Site(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {"name": "login_base", "default": ""},
+ {"name": "slices_ids", "default": [], "fk_reverse": "Slice"},
+ {"name": "leaf_model_name", "default": "Site"},
+ {"name": "class_names", "default": "Site"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(Site, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("Site")
+
+
+class Service(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {"name": "slices_ids", "default": [], "fk_reverse": "Slice"},
+ {"name": "leaf_model_name", "default": "Service"},
+ {"name": "class_names", "default": "Service"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(Service, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("Service")
+
+
+class ServiceInstance(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "owher", "default": 0, "fk_model": "Service"},
+ {"name": "leaf_model_name", "default": "ServiceInstance"},
+ {"name": "class_names", "default": "ServiceInstance"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(ServiceInstance, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("ServiceInstance")
+
+
+class ONOSService(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {"name": "leaf_model_name", "default": "ONOSService"},
+ {"name": "class_names", "default": "ONOSService,Service"},
+ )
+
+ BASES = ["Service"]
+
+ def __init__(self, **kwargs):
+ return super(ONOSService, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("ONOSService")
+
+
+class Network(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {"name": "owner_id", "default": 0, "fk_model": "Slice"},
+ {"name": "template_id", "default": 0, "fk_model": "NetworkTemplate"},
+ {
+ "name": "controllernetworks_ids",
+ "default": [],
+ "fk_reverse": "ControllerNetwork",
+ },
+ {"name": "leaf_model_name", "default": "Network"},
+ {"name": "class_names", "default": "Network"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(Network, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("Network")
+
+
+class NetworkTemplate(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "name", "default": ""},
+ {"name": "vtn_kind", "default": ""},
+ {"name": "leaf_model_name", "default": "NetworkTemplate"},
+ {"name": "class_names", "default": "NetworkTemplate"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(NetworkTemplate, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("NetworkTemplate")
+
+
+class ControllerNetwork(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "network_id", "default": 0, "fk_model": "Network"},
+ {"name": "controller_id", "default": 0, "fk_model": "Controller"},
+ {"name": "leaf_model_name", "default": "ControllerNetwork"},
+ {"name": "class_names", "default": "ControllerNetwork"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(ControllerNetwork, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("ControllerNetwork")
+
+
+class NetworkSlice(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "network_id", "default": 0, "fk_model": "Network"},
+ {"name": "slice_id", "default": 0, "fk_model": "Slice"},
+ {"name": "leaf_model_name", "default": "NetworkSlice"},
+ {"name": "class_names", "default": "NetworkSlice"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(NetworkSlice, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("NetworkSlice")
+
+
+class Tag(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "service_id", "default": None},
+ {"name": "name", "default": ""},
+ {"name": "value", "default": ""},
+ {"name": "content_type", "default": None},
+ {"name": "object_id", "default": None},
+ {"name": "leaf_model_name", "default": "Tag"},
+ {"name": "class_names", "default": "Tag"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(Tag, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("Tag")
+
+
+class TestModel(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "intfield", "default": 0},
+ {"name": "stringfield", "default": "somestring"},
+ {"name": "testmodeltwos_ids", "default": [], "fk_reverse": "TestModelTwo"},
+ {"name": "class_names", "default": "TestModel"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(TestModel, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("TestModel")
+
+
+class TestModelTwo(FakeObj):
+ FIELDS = (
+ {"name": "id", "default": 0},
+ {"name": "intfieldtwo", "default": 0},
+ {"name": "stringfieldtwo", "default": "somestringtwo"},
+ {
+ "name": "testmodel_id",
+ "default": 0,
+ "fk_model": "TestModel",
+ "fk_reverseFieldName": "testmodeltwos",
+ },
+ {"name": "class_names", "default": "TestModel"},
+ )
+
+ def __init__(self, **kwargs):
+ return super(TestModelTwo, self).__init__(self.FIELDS, **kwargs)
+
+ DESCRIPTOR = FakeDescriptor("TestModelTwo")
+
+
+class ID(FakeObj):
+ pass
+
+
+class FakeItemList(object):
+ def __init__(self, items):
+ self.items = items
+
+
+class FakeElement(object):
+ EQUAL = "equal"
+ IEXACT = "iexact"
+
+ def __init__(self):
+ pass
+
+
+class FakeElements(object):
+ def __init__(self):
+ self.items = []
+
+ def add(self):
+ el = FakeElement()
+ self.items.append(el)
+ return el
+
+ def __getitem__(self, index):
+ return self.items[index]
+
+ def __len__(self):
+ return len(self.items)
+
+
+class FakeQuery(object):
+ DEFAULT = 0
+ ALL = 1
+ SYNCHRONIZER_DIRTY_OBJECTS = 2
+ SYNCHRONIZER_DELETED_OBJECTS = 3
+ SYNCHRONIZER_DIRTY_POLICIES = 4
+ SYNCHRONIZER_DELETED_POLICIES = 5
+
+ def __init__(self):
+ self.elements = FakeElements()
+
+
+class FakeStub(object):
+ def __init__(self):
+ self.id_counter = 1
+ self.objs = {}
+ self.deleted_objs = {}
+ for name in [
+ "Controller",
+ "Deployment",
+ "Slice",
+ "Site",
+ "Tag",
+ "Service",
+ "ServiceInstance",
+ "ONOSService",
+ "User",
+ "Network",
+ "NetworkTemplate",
+ "ControllerNetwork",
+ "NetworkSlice",
+ "TestModel",
+ "TestModelTwo",
+ ]:
+ setattr(self, "Get%s" % name, functools.partial(self.get, name))
+ setattr(self, "List%s" % name, functools.partial(self.list, name))
+ setattr(self, "Create%s" % name, functools.partial(self.create, name))
+ setattr(self, "Delete%s" % name, functools.partial(self.delete, name))
+ setattr(self, "Update%s" % name, functools.partial(self.update, name))
+ setattr(self, "Filter%s" % name, functools.partial(self.filter, name))
+
+ def make_key(self, name, id):
+ return "%s:%d" % (name, id.id)
+
+ def get(self, classname, id, metadata=None):
+ obj = self.objs.get(self.make_key(classname, id), None)
+ return obj
+
+ def list(self, classname, empty, metadata=None):
+ items = []
+ for (k, v) in self.objs.items():
+ (this_classname, id) = k.split(":")
+ if this_classname == classname:
+ items.append(v)
+ return FakeItemList(items)
+
+ def filter(self, classname, query, metadata=None):
+ items = []
+
+ if query.kind == FakeQuery.SYNCHRONIZER_DELETED_OBJECTS:
+ objs = list(self.deleted_objs.items())
+ else:
+ objs = list(self.objs.items())
+
+ for (k, v) in objs:
+ (this_classname, id) = k.split(":")
+ if this_classname != classname:
+ continue
+ match = True
+ for q in query.elements.items:
+ iValue = getattr(q, "iValue", None)
+ if (iValue is not None) and getattr(v, q.name) != iValue:
+ match = False
+ sValue = getattr(q, "sValue", None)
+ if (sValue is not None) and getattr(v, q.name) != sValue:
+ match = False
+ if match:
+ items.append(v)
+ return FakeItemList(items)
+
+ def create(self, classname, obj, metadata=None):
+ obj.id = self.id_counter
+ self.id_counter = self.id_counter + 1
+ k = self.make_key(classname, FakeObj(id=obj.id))
+ self.objs[k] = obj
+
+ for base_classname in obj.BASES:
+ base_class = globals()[base_classname]
+ base_obj = base_class(id=obj.id, leaf_model_name=classname)
+ k = self.make_key(base_classname, base_obj)
+ self.objs[k] = base_obj
+
+ return obj
+
+ def update(self, classname, obj, metadata=None):
+ # TODO: partial update support?
+ k = self.make_key(classname, FakeObj(id=obj.id))
+ self.objs[k] = obj
+ return obj
+
+ def delete(self, classname, id, metadata=None):
+ k = self.make_key(classname, id)
+ obj = self.objs[k]
+ del self.objs[k]
+ self.deleted_objs[k] = obj
+
+
+class FakeCommonProtos(object):
+ def __init__(self):
+ self.ID = ID
+ self.Query = FakeQuery
+
+
+class FakeProtos(object):
+ def __init__(self):
+ for name in [
+ "Controller",
+ "Deployment",
+ "Slice",
+ "Site",
+ "ID",
+ "Tag",
+ "Service",
+ "ServiceInstance",
+ "ONOSService",
+ "User",
+ "Network",
+ "NetworkTemplate",
+ "ControllerNetwork",
+ "NetworkSlice",
+ "TestModel",
+ "TestModelTwo",
+ ]:
+ setattr(self, name, globals()[name])
+ self.common__pb2 = FakeCommonProtos()
+
+
+class FakeSymDb(object):
+ def __init__(self):
+ self._classes = {}
+ for name in [
+ "Controller",
+ "Deployment",
+ "Slice",
+ "Site",
+ "ID",
+ "Tag",
+ "Service",
+ "ServiceInstance",
+ "ONOSService",
+ "User",
+ "Network",
+ "NetworkTemplate",
+ "ControllerNetwork",
+ "NetworkSlice",
+ "TestModel",
+ "TestModelTwo",
+ ]:
+ self._classes["xos.%s" % name] = globals()[name]
diff --git a/lib/xos-api/tests/test_chameleon_client.py b/lib/xos-api/tests/test_chameleon_client.py
new file mode 100644
index 0000000..24b19ca
--- /dev/null
+++ b/lib/xos-api/tests/test_chameleon_client.py
@@ -0,0 +1,26 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import xosapi.chameleon_client.grpc_client as chameleon_client
+
+
+class TestChameleonClient(unittest.TestCase):
+
+ def test_chameleon_init(self):
+ """ test that chameleon_client can be loaded """
+
+ cclient = chameleon_client.GrpcClient("consul_endpoint", "work_dir")
+ self.assertEqual(type(cclient), chameleon_client.GrpcClient)
diff --git a/lib/xos-api/tests/test_config.yaml b/lib/xos-api/tests/test_config.yaml
new file mode 100644
index 0000000..ae389f4
--- /dev/null
+++ b/lib/xos-api/tests/test_config.yaml
@@ -0,0 +1,26 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+name: test-coreapi
+logging:
+ version: 1
+ handlers:
+ console:
+ class: logging.StreamHandler
+ loggers:
+ 'multistructlog':
+ handlers:
+ - console
diff --git a/lib/xos-api/tests/test_orm.py b/lib/xos-api/tests/test_orm.py
new file mode 100644
index 0000000..86be969
--- /dev/null
+++ b/lib/xos-api/tests/test_orm.py
@@ -0,0 +1,1090 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+
+import os
+import random
+import string
+import sys
+import unittest
+
+try: # python 3
+ from io import StringIO
+ from unittest.mock import patch
+except ImportError: # python 2
+ from StringIO import StringIO
+ from mock import patch
+
+
+# by default, use fake stub rather than real core
+USE_FAKE_STUB = True
+
+PARENT_DIR = os.path.join(os.path.dirname(__file__), "..")
+
+
+class TestORM(unittest.TestCase):
+ def setUp(self):
+ from xosconfig import Config
+
+ test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+ config = os.path.join(test_path, "test_config.yaml")
+ Config.clear()
+ Config.init(config, "synchronizer-config-schema.yaml")
+ if USE_FAKE_STUB:
+ sys.path.append(PARENT_DIR)
+
+ # Import these after config, in case they depend on config
+ from xosapi.orm import ORMQuerySet, ORMLocalObjectManager
+
+ self.ORMQuerySet = ORMQuerySet
+ self.ORMLocalObjectManager = ORMLocalObjectManager
+
+ def tearDown(self):
+ if USE_FAKE_STUB:
+ sys.path.remove(PARENT_DIR)
+
+ def make_coreapi(self):
+ if USE_FAKE_STUB:
+ import xosapi.orm
+ from fake_stub import FakeStub, FakeProtos, FakeObj
+
+ xosapi.orm.import_convenience_methods()
+
+ stub = FakeStub()
+ api = xosapi.orm.ORMStub(
+ stub=stub,
+ package_name="xos",
+ protos=FakeProtos(),
+ empty=FakeObj,
+ enable_backoff=False,
+ )
+ return api
+ else:
+ return xos_grpc_client.coreapi
+
+ def test_repr_name(self):
+ orm = self.make_coreapi()
+ s = orm.Slice(name="foo")
+ self.assertNotEqual(s, None)
+ self.assertEqual(repr(s), "<Slice: foo>")
+
+ def test_str_name(self):
+ orm = self.make_coreapi()
+ s = orm.Slice(name="foo")
+ self.assertNotEqual(s, None)
+ self.assertEqual(str(s), "foo")
+
+ def test_dumpstr_name(self):
+ orm = self.make_coreapi()
+ s = orm.Slice(name="foo")
+ self.assertNotEqual(s, None)
+ self.assertEqual(s.dumpstr(), 'name: "foo"\n')
+
+ def test_repr_noname(self):
+ orm = self.make_coreapi()
+ s = orm.Slice()
+ self.assertNotEqual(s, None)
+ self.assertEqual(repr(s), "<Slice: id-0>")
+
+ def test_str_noname(self):
+ orm = self.make_coreapi()
+ s = orm.Slice()
+ self.assertNotEqual(s, None)
+ self.assertEqual(str(s), "Slice-0")
+
+ def test_dumpstr_noname(self):
+ orm = self.make_coreapi()
+ s = orm.Slice()
+ self.assertNotEqual(s, None)
+ self.assertEqual(s.dumpstr(), "")
+
+ def test_dump(self):
+ """ dump() is like dumpstr() but prints to stdout. Mock stdout by using a stringIO. """
+
+ orm = self.make_coreapi()
+ s = orm.Slice(name="foo")
+ self.assertNotEqual(s, None)
+ with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
+ s.dump()
+ self.assertEqual(mock_stdout.getvalue(), 'name: "foo"\n\n')
+
+ def test_create(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+
+ def test_get(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ got_site = orm.Site.objects.get(id=site.id)
+ self.assertNotEqual(got_site, None)
+ self.assertEqual(got_site.id, site.id)
+
+ def test_invalidate_cache(self):
+ orm = self.make_coreapi()
+ testModel = orm.TestModel()
+
+ # populate the caches with some placeholders we can test for
+ testModel.cache = {"a": 1}
+ testModel.reverse_cache = {"b": 2}
+
+ testModel.invalidate_cache()
+
+ self.assertEqual(testModel.cache, {})
+ self.assertEqual(testModel.reverse_cache, {})
+
+ def test_save_new(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+
+ def test_save_existing(self):
+ orm = self.make_coreapi()
+ orig_len_sites = len(orm.Site.objects.all())
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+
+ # there should be one new site
+ self.assertEqual(len(orm.Site.objects.all()), orig_len_sites + 1)
+
+ # retrieve the site, and update it
+ created_site_id = site.id
+ site = orm.Site.objects.get(id=created_site_id)
+ site.name = "mysitetwo"
+ site.save()
+
+ # the site_id should not have changed
+ self.assertEqual(site.id, created_site_id)
+
+ # there should still be only one new site
+ self.assertEqual(len(orm.Site.objects.all()), orig_len_sites + 1)
+
+ # the name should have changed
+ self.assertEqual(orm.Site.objects.get(id=created_site_id).name, "mysitetwo")
+
+ def test_delete(self):
+ orm = self.make_coreapi()
+ orig_len_sites = len(orm.Site.objects.all())
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ site.delete()
+ sites = orm.Site.objects.all()
+ self.assertEqual(len(sites), orig_len_sites)
+
+ def test_objects_all(self):
+ orm = self.make_coreapi()
+ orig_len_sites = len(orm.Site.objects.all())
+ site = orm.Site(name="mysite")
+ site.save()
+ sites = orm.Site.objects.all()
+ self.assertEqual(len(sites), orig_len_sites + 1)
+
+ def test_objects_first(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ site = orm.Site.objects.first()
+ self.assertNotEqual(site, None)
+
+ def test_content_type_map(self):
+ orm = self.make_coreapi()
+ self.assertTrue("Slice" in orm.content_type_map.values())
+ self.assertTrue("Site" in orm.content_type_map.values())
+ self.assertTrue("Tag" in orm.content_type_map.values())
+
+ def test_foreign_key_get(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ slice = orm.Slice(name="mysite_foo", site_id=site.id, creator_id=user.id)
+ slice.save()
+ self.assertTrue(slice.id > 0)
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site.id)
+
+ def test_foreign_key_set_with_invalidate(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+ slice.save()
+ slice.invalidate_cache()
+ self.assertTrue(slice.id > 0)
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id in slice.site.slices_ids)
+
+ def test_foreign_key_set_without_invalidate(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+ slice.save()
+ self.assertTrue(slice.id > 0)
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id in slice.site.slices_ids)
+ ids_from_models = [x.id for x in slice.site.slices.all()]
+ self.assertTrue(slice.id in ids_from_models)
+
+ def test_foreign_key_reset(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+ slice.save()
+ self.assertTrue(slice.id > 0)
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id in site.slices_ids)
+ self.assertTrue(slice.id in slice.site.slices_ids)
+
+ site2 = orm.Site(name="mysite2")
+ site2.save()
+ slice.name = "mysite2_foo"
+ slice.site = site2
+ slice.save()
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site2.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id not in site.slices_ids)
+ self.assertTrue(slice.id in site2.slices_ids)
+ self.assertTrue(slice.id in slice.site.slices_ids)
+ ids_from_models1 = [x.id for x in site.slices.all()]
+ self.assertTrue(slice.id not in ids_from_models1)
+ ids_from_models2 = [x.id for x in site2.slices.all()]
+ self.assertTrue(slice.id in ids_from_models2)
+
+ def test_foreign_key_back_and_forth_even(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+ slice.save()
+ self.assertTrue(slice.id > 0)
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id in site.slices_ids)
+ self.assertTrue(slice.id in slice.site.slices_ids)
+
+ site2 = orm.Site(name="mysite2")
+ site2.save()
+ slice.name = "mysite2_foo"
+ slice.site = site2
+ slice.site = site
+ slice.site = site2
+ slice.site = site
+ slice.save()
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id not in site2.slices_ids)
+ self.assertTrue(slice.id in site.slices_ids)
+ self.assertTrue(slice.id in slice.site.slices_ids)
+
+ def test_foreign_key_back_and_forth_odd(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+ slice.save()
+ self.assertTrue(slice.id > 0)
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id in site.slices_ids)
+ self.assertTrue(slice.id in slice.site.slices_ids)
+
+ site2 = orm.Site(name="mysite2")
+ site2.save()
+ slice.name = "mysite2_foo"
+ slice.site = site2
+ slice.site = site
+ slice.site = site2
+ slice.site = site
+ slice.site = site2
+ slice.save()
+ self.assertNotEqual(slice.site, None)
+ self.assertEqual(slice.site.id, site2.id)
+ if not USE_FAKE_STUB:
+ self.assertTrue(slice.id not in site.slices_ids)
+ self.assertTrue(slice.id in site2.slices_ids)
+ self.assertTrue(slice.id in slice.site.slices_ids)
+
+ def test_foreign_key_create_null(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ slice = orm.Slice(
+ name="mysite_foo", site=site, service=None, creator_id=user.id
+ )
+ slice.save()
+ slice.invalidate_cache()
+ self.assertTrue(slice.id > 0)
+ self.assertEqual(slice.service, None)
+
+ def test_foreign_key_set_null(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ self.assertTrue(user.id > 0)
+ service = orm.Service(name="myservice")
+ service.save()
+ self.assertTrue(service.id > 0)
+ # start out slice.service is non-None
+ slice = orm.Slice(
+ name="mysite_foo", site=site, service=service, creator_id=user.id
+ )
+ slice.save()
+ slice.invalidate_cache()
+ self.assertTrue(slice.id > 0)
+ self.assertNotEqual(slice.service, None)
+ self.assertEqual(slice.service.id, service.id)
+ # now set it to None
+ slice.service = None
+ slice.save()
+ slice.invalidate_cache()
+ self.assertEqual(slice.service, None)
+
+ def test_generic_foreign_key_get(self):
+ orm = self.make_coreapi()
+ service = orm.Service(name="myservice")
+ service.save()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ tag = orm.Tag(
+ service=service,
+ name="mytag",
+ value="somevalue",
+ content_type=site.self_content_type_id,
+ object_id=site.id,
+ )
+ tag.save()
+ self.assertTrue(tag.id > 0)
+ self.assertNotEqual(tag.content_object, None)
+ self.assertEqual(tag.content_object.id, site.id)
+
+ def test_generic_foreign_key_get_decl(self):
+ orm = self.make_coreapi()
+ service = orm.Service(name="myservice")
+ service.save()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ tag = orm.Tag(
+ service=service,
+ name="mytag",
+ value="somevalue",
+ content_type=site.self_content_type_id + "_decl",
+ object_id=site.id,
+ )
+ tag.save()
+ self.assertTrue(tag.id > 0)
+ self.assertNotEqual(tag.content_object, None)
+ self.assertEqual(tag.content_object.id, site.id)
+
+ def test_generic_foreign_key_get_bad_contenttype(self):
+ orm = self.make_coreapi()
+ service = orm.Service(name="myservice")
+ service.save()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ tag = orm.Tag(
+ service=service,
+ name="mytag",
+ value="somevalue",
+ content_type="does_not_exist",
+ object_id=site.id,
+ )
+ tag.save()
+ self.assertTrue(tag.id > 0)
+ with self.assertRaises(Exception) as e:
+
+ self.assertEqual(
+ str(e.exception),
+ "Content_type does_not_exist not found in self.content_type_map",
+ )
+
+ def test_generic_foreign_key_get_bad_id(self):
+ orm = self.make_coreapi()
+ service = orm.Service(name="myservice")
+ service.save()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ tag = orm.Tag(
+ service=service,
+ name="mytag",
+ value="somevalue",
+ content_type=site.self_content_type_id,
+ object_id=4567,
+ )
+ tag.save()
+ self.assertTrue(tag.id > 0)
+ with self.assertRaises(Exception) as e:
+ self.assertEqual(
+ str(e.exception), "Object 4567 of model Site was not found"
+ )
+
+ def test_generic_foreign_key_set(self):
+ orm = self.make_coreapi()
+ service = orm.Service(name="myservice")
+ service.save()
+ site = orm.Site(name="mysite")
+ site.save()
+ self.assertTrue(site.id > 0)
+ tag = orm.Tag(service=service, name="mytag", value="somevalue")
+ tag.content_object = site
+ tag.invalidate_cache()
+ self.assertEqual(tag.content_type, site.self_content_type_id)
+ self.assertEqual(tag.object_id, site.id)
+ tag.save()
+ self.assertTrue(tag.id > 0)
+ self.assertNotEqual(tag.content_object, None)
+ self.assertEqual(tag.content_object.id, site.id)
+
+ def test_leaf_model_trivial(self):
+ orm = self.make_coreapi()
+ service = orm.Service(name="myservice")
+ service.save()
+ self.assertEqual(service.leaf_model_name, "Service")
+
+ def test_leaf_model_descendant(self):
+ orm = self.make_coreapi()
+ onos_service = orm.ONOSService(name="myservice")
+ onos_service.save()
+ self.assertEqual(onos_service.model_name, "ONOSService")
+ self.assertEqual(onos_service.leaf_model_name, "ONOSService")
+
+ service = orm.Service.objects.get(id=onos_service.id)
+ self.assertEqual(service.id, onos_service.id)
+ self.assertEqual(service.model_name, "Service")
+ self.assertEqual(service.leaf_model_name, "ONOSService")
+
+ onos_service_cast = service.leaf_model
+ self.assertEqual(onos_service_cast.model_name, "ONOSService")
+ self.assertEqual(onos_service_cast.leaf_model_name, "ONOSService")
+ self.assertEqual(onos_service_cast.id, onos_service.id)
+
+ def test_field_null(self):
+ """ In a saved object, if a nullable field is left set to None, make sure the ORM returns None """
+
+ orm = self.make_coreapi()
+ tm = orm.TestModel()
+ tm.save()
+
+ tm = orm.TestModel.objects.all()[0]
+ self.assertFalse(tm._wrapped_class.HasField("intfield"))
+ self.assertEqual(tm.intfield, None)
+
+ def test_field_null_new(self):
+ """ For models that haven't been saved yet, reading the field should return the gRPC default """
+
+ orm = self.make_coreapi()
+ tm = orm.TestModel()
+
+ self.assertEqual(tm.intfield, 0)
+
+ def test_field_non_null(self):
+ """ In a saved object, if a nullable field is set to a value, then make sure the ORM returns the value """
+
+ orm = self.make_coreapi()
+ tm = orm.TestModel(intfield=3)
+ tm.save()
+
+ tm = orm.TestModel.objects.all()[0]
+ self.assertEqual(tm.intfield, 3)
+
+ def test_field_set_null(self):
+ """ Setting a field to None is not allowed """
+
+ orm = self.make_coreapi()
+ tm = orm.TestModel()
+ with self.assertRaises(Exception) as e:
+ tm.intfile = None
+ self.assertEqual(
+ str(e.exception),
+ "Setting a non-foreignkey field to None is not supported",
+ )
+
+ def test_query_iexact(self):
+ orm = self.make_coreapi()
+ with patch.object(orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
+ orm.TestModel.objects.filter(name__iexact="foo")
+ self.assertEqual(filter.call_count, 1)
+ q = filter.call_args[0][0]
+
+ self.assertEqual(q.kind, q.DEFAULT)
+ self.assertEqual(len(q.elements), 1)
+ self.assertEqual(q.elements[0].operator, q.elements[0].IEXACT)
+ self.assertEqual(q.elements[0].sValue, "foo")
+
+ def test_query_equal(self):
+ orm = self.make_coreapi()
+ with patch.object(orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
+ orm.TestModel.objects.filter(name="foo")
+ self.assertEqual(filter.call_count, 1)
+ q = filter.call_args[0][0]
+
+ self.assertEqual(q.kind, q.DEFAULT)
+ self.assertEqual(len(q.elements), 1)
+ self.assertEqual(q.elements[0].operator, q.elements[0].EQUAL)
+ self.assertEqual(q.elements[0].sValue, "foo")
+
+ def test_ORMWrapper_dict(self):
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel(intfield=7, stringfield="foo")
+
+ self.assertDictEqual(testModel._dict, {"intfield": 7, "stringfield": "foo"})
+
+ def test_ORMWrapper_new_diff(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite")
+
+ self.assertEqual(site.is_new, True)
+ self.assertEqual(site._dict, {"name": "mysite"})
+ self.assertEqual(site.diff, {})
+ self.assertEqual(site.changed_fields, ["name"])
+ self.assertEqual(site.has_field_changed("name"), False)
+ self.assertEqual(site.has_field_changed("login_base"), False)
+
+ site.login_base = "bar"
+
+ self.assertEqual(site._dict, {"login_base": "bar", "name": "mysite"})
+ self.assertEqual(site.diff, {"login_base": (None, "bar")})
+ self.assertIn("name", site.changed_fields)
+ self.assertIn("login_base", site.changed_fields)
+ self.assertEqual(site.has_field_changed("name"), False)
+ self.assertEqual(site.has_field_changed("login_base"), True)
+ self.assertEqual(site.get_field_diff("login_base"), (None, "bar"))
+
+ def test_ORMWrapper_existing_diff(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite", login_base="foo")
+ site.save()
+ site = orm.Site.objects.first()
+
+ self.assertEqual(site.is_new, False)
+ self.assertEqual(site._dict, {"id": 1, "name": "mysite", "login_base": "foo"})
+ self.assertEqual(site.diff, {})
+ self.assertEqual(site.changed_fields, [])
+ self.assertEqual(site.has_field_changed("name"), False)
+ self.assertEqual(site.has_field_changed("login_base"), False)
+
+ site.login_base = "bar"
+
+ self.assertEqual(site._dict, {"id": 1, "login_base": "bar", "name": "mysite"})
+ self.assertEqual(site.diff, {"login_base": ("foo", "bar")})
+ self.assertIn("login_base", site.changed_fields)
+ self.assertEqual(site.has_field_changed("name"), False)
+ self.assertEqual(site.has_field_changed("login_base"), True)
+
+ def test_ORMWrapper_diff_after_save(self):
+ orm = self.make_coreapi()
+ site = orm.Site(name="mysite", login_base="foo")
+ site.save()
+ site = orm.Site.objects.first()
+
+ self.assertEqual(site.diff, {})
+
+ site.login_base = "bar"
+
+ self.assertEqual(site.diff, {"login_base": ("foo", "bar")})
+
+ site.save()
+
+ self.assertEqual(site.diff, {})
+
+ def test_ORMWrapper_recompute_initial(self):
+ """ For saved models, Recompute_initial should take recompute the set of initial values, removing all items
+ from the diff set.
+ """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ testModel.save()
+
+ testModel.intfield = 9
+ self.assertEqual(testModel.changed_fields, ["intfield"])
+
+ testModel.recompute_initial()
+ self.assertEqual(testModel.changed_fields, [])
+
+ def test_ORMWrapper_create_attr(self):
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ testModel.create_attr("some_new_attribute", "foo")
+ self.assertEqual(testModel.some_new_attribute, "foo")
+
+ def test_ORMWrapper_save_changed_fields(self):
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel(intfield=7, stringfield="foo")
+ testModel.save()
+
+ testModel.intfield = 9
+
+ with patch.object(
+ orm.grpc_stub, "UpdateTestModel", wraps=orm.grpc_stub.UpdateTestModel
+ ) as update:
+ testModel.save_changed_fields()
+
+ self.assertEqual(update.call_count, 1)
+ self.assertIn("metadata", update.call_args[1])
+ update_fields_arg = [
+ x[1] for x in update.call_args[1]["metadata"] if x[0] == "update_fields"
+ ]
+ self.assertEqual(update_fields_arg, ["intfield"])
+
+ def test_ORMWrapper_get_generic_foreignkeys(self):
+ """ Currently this is a placeholder that returns an empty list """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ self.assertEqual(testModel.get_generic_foreignkeys(), [])
+
+ def test_ORMWrapper_gen_fkmap(self):
+ """ TestModelTwo includes a foreignkey relation to TestModel, and the fkmap should contain that relation """
+
+ orm = self.make_coreapi()
+
+ testModelTwo = orm.TestModelTwo()
+
+ self.assertDictEqual(
+ testModelTwo.gen_fkmap(),
+ {
+ "testmodel": {
+ "kind": "fk",
+ "modelName": "TestModel",
+ "reverse_fieldName": "testmodeltwos",
+ "src_fieldName": "testmodel_id",
+ }
+ },
+ )
+
+ def test_ORMWrapper_gen_reverse_fkmap(self):
+ """ TestModel includes a reverse relation back to TestModelTwo, and the reverse_fkmap should contain that
+ relation.
+ """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+
+ self.assertDictEqual(
+ testModel.gen_reverse_fkmap(),
+ {
+ "testmodeltwos": {
+ "modelName": "TestModelTwo",
+ "src_fieldName": "testmodeltwos_ids",
+ "writeable": False,
+ }
+ },
+ )
+
+ def test_ORMWrapper_fk_resolve(self):
+ """ If we create a TestModelTwo that has a foreign key reference to a TestModel, then calling fk_resolve should
+ return that model.
+ """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ testModel.save()
+
+ testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+
+ testModel_resolved = testModelTwo.fk_resolve("testmodel")
+ self.assertEqual(testModel_resolved.id, testModel.id)
+
+ # the cache should have been populated
+ self.assertIn(("testmodel", testModel_resolved), testModelTwo.cache.items())
+
+ def test_ORMWrapper_reverse_fk_resolve(self):
+ """ If a TestModelTwo has a relation to TestModel, then TestModel's reverse_fk should be resolvable to a list
+ of TestModelTwo objects.
+ """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ testModel.save()
+
+ testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+ testModelTwo.save()
+
+ # fake_stub.py doesn't populate the reverse relations for us, so force what the server would have done...
+ testModel._wrapped_class.testmodeltwos_ids = [testModelTwo.id]
+
+ testModelTwos_resolved = testModel.reverse_fk_resolve("testmodeltwos")
+ self.assertEqual(testModelTwos_resolved.count(), 1)
+
+ # the reverse_cache should have been populated
+ self.assertIn(
+ ("testmodeltwos", testModelTwos_resolved), testModel.reverse_cache.items()
+ )
+
+ def test_ORMWrapper_fk_set(self):
+ """ fk_set will set the testmodel field on TesTModelTwo to point to the TestModel. """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ testModel.save()
+
+ testModelTwo = orm.TestModelTwo()
+
+ testModelTwo.fk_set("testmodel", testModel)
+
+ self.assertEqual(testModelTwo.testmodel_id, testModel.id)
+
+ def test_ORMWrapper_post_save_fixups_remove(self):
+ """ Apply a post_save_fixup that removes a reverse foreign key """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ testModel.save()
+
+ testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+
+ # fake_stub.py doesn't populate the reverse relations for us, so force what the server would have done...
+ testModel._wrapped_class.testmodeltwos_ids = [testModelTwo.id]
+
+ post_save_fixups = [
+ {
+ "src_fieldName": "testmodel",
+ "dest_id": None, # this field appears to not be used...
+ "dest_model": testModel,
+ "remove": True,
+ "reverse_fieldName": "testmodeltwos",
+ }
+ ]
+
+ testModelTwo.post_save_fixups = post_save_fixups
+ testModelTwo.do_post_save_fixups()
+
+ self.assertEqual(testModel._wrapped_class.testmodeltwos_ids, [])
+
+ def test_ORMWrapper_post_save_fixups_add(self):
+ """ Apply a post_save_fixup that adds a reverse foreign key """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel()
+ testModel.save()
+
+ testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+ testModelTwo.save()
+
+ # Make sure the reverse_relation is unpopulated. This should be the case, as fake_stub.py() doesn't populate
+ # the reverse relation. But let's be sure, in case someone fixes that.
+ testModel._wrapped_class.testmodeltwos_ids = []
+
+ post_save_fixups = [
+ {
+ "src_fieldName": "testmodel",
+ "dest_id": None, # this field appears to not be used...
+ "dest_model": testModel,
+ "remove": False,
+ "reverse_fieldName": "testmodeltwos",
+ }
+ ]
+
+ testModelTwo.post_save_fixups = post_save_fixups
+ testModelTwo.do_post_save_fixups()
+
+ self.assertEqual(testModel._wrapped_class.testmodeltwos_ids, [testModelTwo.id])
+
+ def test_ORMWrapper_tologdict(self):
+ """ Tologdict contains the model name and id, used for structured logging """
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel(intfield=7, stringfile="foo")
+
+ self.assertDictEqual(
+ testModel.tologdict(), {"model_name": "TestModel", "pk": 0}
+ )
+
+ def test_ORMWrapper_ansible_tag(self):
+ """ Ansible_tag is used by old-style synchronizers. Deprecated. """
+
+ orm = self.make_coreapi()
+
+ testModel = orm.TestModel(id=7)
+
+ self.assertEqual(testModel.ansible_tag, "TestModel_7")
+
+ def test_deleted_objects_all(self):
+ orm = self.make_coreapi()
+ orig_len_sites = len(orm.Site.objects.all())
+ orig_len_deleted_sites = len(orm.Site.deleted_objects.all())
+ site = orm.Site(name="mysite")
+ site.save()
+ site.delete()
+ sites = orm.Site.objects.all()
+ self.assertEqual(len(sites), orig_len_sites)
+ deleted_sites = orm.Site.deleted_objects.all()
+ self.assertEqual(len(deleted_sites), orig_len_deleted_sites + 1)
+
+ def test_deleted_objects_filter(self):
+ orm = self.make_coreapi()
+ with patch.object(
+ orm.grpc_stub, "FilterTestModel", wraps=orm.grpc_stub.FilterTestModel
+ ) as filter:
+ foo = orm.TestModel(name="foo")
+ foo.save()
+ foo.delete()
+
+ # There should be no live objects
+ objs = orm.TestModel.objects.filter(name="foo")
+ self.assertEqual(len(objs), 0)
+
+ # There should be one deleted object
+ deleted_objs = orm.TestModel.deleted_objects.filter(name="foo")
+ self.assertEqual(len(deleted_objs), 1)
+
+ # Two calls, one for when we checked live objects, the other for when we checked deleted objects
+ self.assertEqual(filter.call_count, 2)
+ q = filter.call_args[0][0]
+
+ # Now spy on the query that was generated, to make sure it looks like we expect
+ self.assertEqual(q.kind, q.SYNCHRONIZER_DELETED_OBJECTS)
+ self.assertEqual(len(q.elements), 1)
+ self.assertEqual(q.elements[0].operator, q.elements[0].EQUAL)
+ self.assertEqual(q.elements[0].sValue, "foo")
+
+ def test_ORMQuerySet_first_nonempty(self):
+ qs = self.ORMQuerySet([1, 2, 3])
+ self.assertEqual(qs.first(), 1)
+
+ def test_ORMQuerySet_first_empty(self):
+ qs = self.ORMQuerySet([])
+ self.assertEqual(qs.first(), None)
+
+ def test_ORMQuerySet_exists_nonempty(self):
+ qs = self.ORMQuerySet([1, 2, 3])
+ self.assertEqual(qs.exists(), True)
+
+ def test_ORMQuerySet_exists_empty(self):
+ qs = self.ORMQuerySet()
+ self.assertEqual(qs.exists(), False)
+
+ def test_ORMLocalObjectManager_nonempty(self):
+ """ Test all(), first(), exists(), and count() together since they're all closely related. Use a nonempty
+ list.
+ """
+ orm = self.make_coreapi()
+
+ t = orm.TestModel()
+ t.save()
+
+ lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [t.id], False)
+ self.assertEqual(len(lobjs.all()), 1)
+ self.assertEqual(lobjs.all()[0].id, t.id)
+ self.assertEqual(lobjs.exists(), True)
+ self.assertEqual(lobjs.count(), 1)
+ self.assertEqual(lobjs.first().id, t.id)
+
+ def test_ORMLocalObjectManager_empty(self):
+ """ Test all(), first(), exists(), and count() together since they're all closely related. Use an empty
+ list.
+ """
+ orm = self.make_coreapi()
+
+ t = orm.TestModel()
+ t.save()
+
+ lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [], False)
+ self.assertEqual(len(lobjs.all()), 0)
+ self.assertEqual(lobjs.exists(), False)
+ self.assertEqual(lobjs.count(), 0)
+ self.assertEqual(lobjs.first(), None)
+
+ def test_ORMLocalObjectManager_not_writeable(self):
+ """ An ORMLocalObjectManager that is not writeable should throw exceptions on add() and remove() """
+ orm = self.make_coreapi()
+
+ t = orm.TestModel()
+ t.save()
+
+ lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [t.id], False)
+
+ with self.assertRaises(Exception) as e:
+ lobjs.add(123)
+ self.assertEqual(str(e.exception), "Only ManyToMany lists are writeable")
+
+ with self.assertRaises(Exception) as e:
+ lobjs.remove(123)
+ self.assertEqual(str(e.exception), "Only ManyToMany lists are writeable")
+
+ def test_ORMLocalObjectManager_add(self):
+ orm = self.make_coreapi()
+
+ t = orm.TestModel()
+ t.save()
+
+ lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [], True)
+ lobjs.add(t)
+ self.assertEqual(lobjs.count(), 1)
+ self.assertEqual(lobjs.first().id, t.id)
+
+ def test_ORMLocalObjectManager_remove(self):
+ orm = self.make_coreapi()
+
+ t = orm.TestModel()
+ t.save()
+
+ lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [t.id], True)
+ lobjs.remove(t)
+ self.assertEqual(lobjs.count(), 0)
+
+
+def main():
+ global USE_FAKE_STUB
+ global xos_grpc_client
+
+ # Command-line argument of -R will cause this test to use a real grpc server
+ # rather than the fake stub.
+
+ if "-R" in sys.argv:
+ USE_FAKE_STUB = False
+ sys.argv.remove("-R")
+ # Note: will leave lots of litter (users, sites, etc) behind in the database
+
+ if USE_FAKE_STUB:
+ unittest.main()
+ else:
+ # This assumes xos-client python library is installed, and a gRPC server
+ # is available.
+
+ from xosapi import xos_grpc_client
+
+ print("Using xos-client library and core server")
+
+ def test_callback():
+ try:
+ sys.argv = sys.argv[
+ :1
+ ]
+ # unittest does not like xos_grpc_client's command line
+ # arguments (TODO: find a cooperative approach)
+ unittest.main()
+ except SystemExit as e:
+ global exitStatus
+ exitStatus = e.code
+
+ xos_grpc_client.start_api_parseargs(test_callback)
+
+ sys.exit(exitStatus)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lib/xos-api/tests/test_wrapper.py b/lib/xos-api/tests/test_wrapper.py
new file mode 100644
index 0000000..8cb5255
--- /dev/null
+++ b/lib/xos-api/tests/test_wrapper.py
@@ -0,0 +1,230 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+
+import os
+import random
+import string
+import sys
+import unittest
+
+# Command-line argument of -R will cause this test to use a real grpc server
+# rather than the fake stub.
+
+# TODO: Investigate writing wrapper unit tests using mocks rather than using the ORM test framework
+
+# by default, use fake stub rather than real core
+USE_FAKE_STUB = True
+
+PARENT_DIR = os.path.join(os.path.dirname(__file__), "..")
+
+
+class TestWrappers(unittest.TestCase):
+ def setUp(self):
+ from xosconfig import Config
+
+ test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+ config = os.path.join(test_path, "test_config.yaml")
+ Config.clear()
+ Config.init(config, "synchronizer-config-schema.yaml")
+
+ if USE_FAKE_STUB:
+ sys.path.append(PARENT_DIR)
+
+ def tearDown(self):
+ if USE_FAKE_STUB:
+ sys.path.remove(PARENT_DIR)
+
+ def make_coreapi(self):
+ if USE_FAKE_STUB:
+ import xosapi.orm
+ from fake_stub import FakeStub, FakeObj, FakeProtos
+
+ xosapi.orm.import_convenience_methods()
+
+ stub = FakeStub()
+ api = xosapi.orm.ORMStub(
+ stub=stub,
+ package_name="xos",
+ protos=FakeProtos(),
+ empty=FakeObj,
+ enable_backoff=False,
+ )
+ return api
+ else:
+ return xos_grpc_client.coreapi
+
+ def test_service_get_composable_networks(self):
+ orm = self.make_coreapi()
+ deployment = orm.Deployment(name="test_deployment")
+ deployment.save()
+ controller = orm.Controller(name="test_controller", deployment_id=deployment.id)
+ controller.save()
+ site = orm.Site(name="testsite")
+ site.save()
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ vsg_access_template = orm.NetworkTemplate(name="vsg_access", vtn_kind="VSG")
+ vsg_access_template.save()
+ service_one = orm.Service(name="service_one")
+ service_one.save()
+ slice_one = orm.Slice(
+ name="testsite_sliceone",
+ service_id=service_one.id,
+ site_id=site.id,
+ creator_id=user.id,
+ network="noauto",
+ )
+ slice_one.save()
+ network_one = orm.Network(
+ name="testsite_sliceone_access",
+ owner_id=slice_one.id,
+ template_id=vsg_access_template.id,
+ )
+ network_one.save()
+ ns = orm.NetworkSlice(slice_id=slice_one.id, network_id=network_one.id)
+ ns.save()
+ cn_one = orm.ControllerNetwork(
+ network_id=network_one.id, controller_id=controller.id
+ )
+ cn_one.save()
+
+ if USE_FAKE_STUB:
+ # fake_Stub doesn't handle reverse foreign keys
+ service_one.slices_ids = [slice_one.id]
+ slice_one.networks_ids = [network_one.id]
+ network_one.controllernetworks_ids = [cn_one.id]
+
+ # make sure we're using a fresh copy of the object, with all its foreign keys filled in
+ service_one = orm.Service.objects.get(id=service_one.id)
+
+ cns = service_one.get_composable_networks()
+ self.assertEqual(len(cns), 1)
+ self.assertEqual(cns[0].id, network_one.id)
+
+ def test_service_get_service_instance_class_name(self):
+ orm = self.make_coreapi()
+ deployment = orm.Deployment(name="test_deployment")
+ deployment.save()
+ controller = orm.Controller(name="test_controller", deployment_id=deployment.id)
+ controller.save()
+ site = orm.Site(name="testsite")
+ site.save()
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ vsg_access_template = orm.NetworkTemplate(name="vsg_access", vtn_kind="VSG")
+ vsg_access_template.save()
+ service_one = orm.Service(name="service_one")
+ service_one.save()
+
+ self.assertEqual(
+ service_one.get_service_instance_class_name(), "ServiceInstance"
+ )
+
+ def test_service_get_service_instance_class(self):
+ orm = self.make_coreapi()
+ deployment = orm.Deployment(name="test_deployment")
+ deployment.save()
+ controller = orm.Controller(name="test_controller", deployment_id=deployment.id)
+ controller.save()
+ site = orm.Site(name="testsite")
+ site.save()
+ user = orm.User(
+ email="fake_"
+ + "".join(
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+ ),
+ site_id=site.id,
+ )
+ user.save()
+ vsg_access_template = orm.NetworkTemplate(name="vsg_access", vtn_kind="VSG")
+ vsg_access_template.save()
+ service_one = orm.Service(name="service_one")
+ service_one.save()
+
+ self.assertEqual(
+ service_one.get_service_instance_class().model_name, "ServiceInstance"
+ )
+
+ def test_wrapper_from__class__dot_name(self):
+ """ The Service model has a wrapper, so it should be returned when make_ORMWrapper looks for a wrapper based
+ on the class name.
+ """
+ orm = self.make_coreapi()
+ obj = orm.Service()
+ self.assertEqual(obj.__class__.__name__, "ORMWrapperService")
+
+ def test_wrapper_from_class_names(self):
+ """ ONOSService._wrapped_class.class_names is "ONOSService, Service" so we should fall back to getting the
+ Service wrapper.
+ """
+ orm = self.make_coreapi()
+ obj = orm.ONOSService()
+ self.assertEqual(obj.__class__.__name__, "ORMWrapperService")
+
+
+def main():
+ global USE_FAKE_STUB
+ global xos_grpc_client
+
+ # Command-line argument of -R will cause this test to use a real grpc server
+ # rather than the fake stub.
+
+ if "-R" in sys.argv:
+ USE_FAKE_STUB = False
+ sys.argv.remove("-R")
+ # Note: will leave lots of litter (users, sites, etc) behind in the database
+
+ if USE_FAKE_STUB:
+ unittest.main()
+ else:
+ # This assumes xos-client python library is installed, and a gRPC server
+ # is available.
+
+ from xosapi import xos_grpc_client
+
+ print("Using xos-client library and core server")
+
+ def test_callback():
+ try:
+ sys.argv = sys.argv[
+ :1
+ ]
+ # unittest does not like xos_grpc_client's command line arguments
+ # (TODO: find a cooperative approach)
+ unittest.main()
+ except SystemExit as e:
+ global exitStatus
+ exitStatus = e.code
+
+ xos_grpc_client.start_api_parseargs(test_callback)
+
+ sys.exit(exitStatus)
+
+
+if __name__ == "__main__":
+ main()