SEBA-382 deleted_objects support in orm
Change-Id: Id012c627d59f18e95bd9acb5b7c7d55e74686694
diff --git a/VERSION b/VERSION
index 5473cf8..0789fdb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.1.26-dev
+2.1.26
diff --git a/containers/chameleon/Dockerfile.chameleon b/containers/chameleon/Dockerfile.chameleon
index 8f27a20..4d8b8c2 100644
--- a/containers/chameleon/Dockerfile.chameleon
+++ b/containers/chameleon/Dockerfile.chameleon
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/chameleon
-FROM xosproject/xos-base:2.1.25
+FROM xosproject/xos-base:2.1.26
# xos-base already has protoc and dependencies installed
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index 9b446f3..55bbbbb 100644
--- a/containers/xos/Dockerfile.client
+++ b/containers/xos/Dockerfile.client
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/xos-client
-FROM xosproject/xos-libraries:2.1.25
+FROM xosproject/xos-libraries:2.1.26
# Install XOS client
COPY xos/xos_client /tmp/xos_client
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index 766cca7..e04dc3d 100644
--- a/containers/xos/Dockerfile.libraries
+++ b/containers/xos/Dockerfile.libraries
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/xos-libraries
-FROM xosproject/xos-base:2.1.25
+FROM xosproject/xos-base:2.1.26
# Add libraries
COPY lib /opt/xos/lib
diff --git a/containers/xos/Dockerfile.synchronizer-base b/containers/xos/Dockerfile.synchronizer-base
index e00018f..dd42166 100644
--- a/containers/xos/Dockerfile.synchronizer-base
+++ b/containers/xos/Dockerfile.synchronizer-base
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/xos-synchronizer-base
-FROM xosproject/xos-client:2.1.25
+FROM xosproject/xos-client:2.1.26
COPY xos/synchronizers/new_base /opt/xos/synchronizers/new_base
COPY xos/xos/logger.py /opt/xos/xos/logger.py
diff --git a/containers/xos/Dockerfile.xos-core b/containers/xos/Dockerfile.xos-core
index bb1a7e7..2ccb16f 100644
--- a/containers/xos/Dockerfile.xos-core
+++ b/containers/xos/Dockerfile.xos-core
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/xos-core
-FROM xosproject/xos-libraries:2.1.25
+FROM xosproject/xos-libraries:2.1.26
# Install XOS
ADD xos /opt/xos
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index fb47e5d..2f21dbc 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -589,28 +589,42 @@
log.exception("Exception in apihelper.list")
raise
+ def build_filter(self, request, query=None):
+ """ Given a filter request, turn it into a django query.
+
+ If argument query is not None, then the new query will be appended to the existing query.
+ """
+ for element in request.elements:
+ if query:
+ query = query & self.query_element_to_q(element)
+ else:
+ query = self.query_element_to_q(element)
+ return query
+
def filter(self, djangoClass, user, request):
try:
- query = None
if request.kind == request.DEFAULT:
- for element in request.elements:
- if query:
- query = query & self.query_element_to_q(element)
- else:
- query = self.query_element_to_q(element)
+ query = self.build_filter(request, None)
queryset = djangoClass.objects.filter(query)
elif request.kind == request.SYNCHRONIZER_DIRTY_OBJECTS:
query = (Q(enacted=None) | Q(enacted__lt=F('updated')) | Q(enacted__lt=F('changed_by_policy'))) \
& Q(lazy_blocked=False) & Q(no_sync=False)
+ query = self.build_filter(request, query)
queryset = djangoClass.objects.filter(query)
elif request.kind == request.SYNCHRONIZER_DELETED_OBJECTS:
- queryset = djangoClass.deleted_objects.all()
+ query = self.build_filter(request, None)
+ if query:
+ queryset = djangoClass.deleted_objects.filter(query)
+ else:
+ queryset = djangoClass.deleted_objects.all()
elif request.kind == request.SYNCHRONIZER_DIRTY_POLICIES:
query = (Q(policed=None) | Q(policed__lt=F('updated')) | Q(policed__lt=F('changed_by_step'))) \
& Q(no_policy=False)
+ query = self.build_filter(request, query)
queryset = djangoClass.objects.filter(query)
elif request.kind == request.SYNCHRONIZER_DELETED_POLICIES:
query = Q(policed__lt=F('updated')) | Q(policed=None)
+ query = self.build_filter(request, query)
queryset = djangoClass.deleted_objects.filter(query)
elif request.kind == request.ALL:
queryset = djangoClass.objects.all()
diff --git a/xos/xos_client/xosapi/fake_stub.py b/xos/xos_client/xosapi/fake_stub.py
index 8cc60f7..13d684d 100644
--- a/xos/xos_client/xosapi/fake_stub.py
+++ b/xos/xos_client/xosapi/fake_stub.py
@@ -353,7 +353,12 @@
return len(self.items)
class FakeQuery(object):
- DEFAULT="default"
+ 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()
@@ -362,6 +367,7 @@
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"]:
@@ -390,17 +396,25 @@
def filter(self, classname, query, metadata=None):
items = []
- for (k,v) in self.objs.items():
+
+ if query.kind == FakeQuery.SYNCHRONIZER_DELETED_OBJECTS:
+ objs = self.deleted_objs.items()
+ else:
+ objs = 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:
- continue
+ match = False
sValue = getattr(q, "sValue", None)
if (sValue is not None) and getattr(v, q.name) != sValue:
- continue
+ match = False
+ if match:
items.append(v)
return FakeItemList(items)
@@ -426,7 +440,9 @@
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):
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index 0ea714b..c4097f1 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -486,15 +486,18 @@
""" Manages a remote list of objects """
# constants better agree with common.proto
+ DEFAULT = 0
+ ALL = 1
SYNCHRONIZER_DIRTY_OBJECTS = 2
SYNCHRONIZER_DELETED_OBJECTS = 3
SYNCHRONIZER_DIRTY_POLICIES = 4
SYNCHRONIZER_DELETED_POLICIES = 5
- def __init__(self, stub, modelName, packageName):
+ def __init__(self, stub, modelName, packageName, kind=0):
self._stub = stub
self._modelName = modelName
self._packageName = packageName
+ self._kind = kind
def wrap_single(self, obj):
return make_ORMWrapper(obj, self._stub)
@@ -506,17 +509,20 @@
return ORMQuerySet(result)
def all(self):
- return self.wrap_list(self._stub.invoke("List%s" % self._modelName, self._stub.make_empty()))
+ if (self._kind == self.DEFAULT):
+ return self.wrap_list(self._stub.invoke("List%s" % self._modelName, self._stub.make_empty()))
+ else:
+ return self.filter()
def first(self):
- objs=self.wrap_list(self._stub.invoke("List%s" % self._modelName, self._stub.make_empty()))
+ objs = self.all()
if not objs:
return None
return objs[0]
def filter(self, **kwargs):
q = self._stub.make_Query()
- q.kind = q.DEFAULT
+ q.kind = self._kind
for (name, val) in kwargs.items():
el = q.elements.add()
@@ -562,6 +568,9 @@
return objs[0]
def new(self, **kwargs):
+ if (self._kind != ORMObjectManager.DEFAULT):
+ raise Exception("Creating objects is only supported by the DEFAULT object manager")
+
cls = self._stub.all_grpc_classes[self._modelName]
o = make_ORMWrapper(cls(), self._stub, is_new=True)
for (k,v) in kwargs.items():
@@ -574,6 +583,7 @@
self.model_name = model_name
self._stub = stub
self.objects = ORMObjectManager(stub, model_name, package_name)
+ self.deleted_objects = ORMObjectManager(stub, model_name, package_name, ORMObjectManager.SYNCHRONIZER_DELETED_OBJECTS)
@property
def __name__(self):
diff --git a/xos/xos_client/xosapi/test_orm.py b/xos/xos_client/xosapi/test_orm.py
index 4c3cd32..00dfeba 100644
--- a/xos/xos_client/xosapi/test_orm.py
+++ b/xos/xos_client/xosapi/test_orm.py
@@ -537,6 +537,43 @@
self.assertEqual(site.diff, {})
+ 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 main():
global USE_FAKE_STUB