CORD-762 add set_fk support to orm
Change-Id: If86d5c041f3ea293aa2b109d881454cae95dd29a
diff --git a/xos/xos_client/tests/orm_listall.py b/xos/xos_client/tests/orm_listall.py
new file mode 100644
index 0000000..d76be05
--- /dev/null
+++ b/xos/xos_client/tests/orm_listall.py
@@ -0,0 +1,27 @@
+import sys
+sys.path.append("..")
+
+from xosapi import xos_grpc_client
+
+def test_callback():
+ print "TEST: orm_listall_crud"
+
+ c = xos_grpc_client.coreclient
+
+ for model_name in c.xos_orm.all_model_names:
+ model_class = getattr(c.xos_orm, model_name)
+
+ try:
+ print " list all %s ..." % model_name,
+
+ objs = model_class.objects.all()
+
+ print "[%d] okay" % len(objs)
+ except:
+ print " fail!"
+ traceback.print_exc()
+
+ print " done"
+
+xos_grpc_client.start_api_parseargs(test_callback)
+
diff --git a/xos/xos_client/tests/orm_user_crud.py b/xos/xos_client/tests/orm_user_crud.py
new file mode 100644
index 0000000..45bda8f
--- /dev/null
+++ b/xos/xos_client/tests/orm_user_crud.py
@@ -0,0 +1,63 @@
+import sys
+sys.path.append("..")
+
+from xosapi import xos_grpc_client
+
+def test_callback():
+ print "TEST: orm_user_crud"
+
+ c = xos_grpc_client.coreclient
+
+ # create a new user and save it
+ u=c.xos_orm.User.objects.new()
+ assert(u.id==0)
+ import random, string
+ u.email=''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
+ u.site=c.xos_orm.Site.objects.all()[0]
+ u.save()
+
+ # when we created the user, he should be assigned an id
+ orig_id = u.id
+ assert(orig_id!=0)
+
+ # invalidate u.site so it's reloaded from the server
+ u.invalidate_cache("site")
+
+ # site object should be populated
+ assert(u.site is not None)
+
+ # site object should have a backpointer to user
+ u_all = u.site.users.all()
+ u_all = [x for x in u_all if x.email == u.email]
+ assert(len(u_all)==1)
+
+ # update the user
+ u.password="foobar"
+ u.save()
+
+ # update should not have changed it
+ assert(u.id==orig_id)
+
+ # check a listall and make sure the user is listed
+ u_all = c.xos_orm.User.objects.all()
+ u_all = [x for x in u_all if x.email == u.email]
+ assert(len(u_all)==1)
+ u2 = u_all[0]
+ assert(u2.id == u.id)
+
+ # get and make sure the password was updated
+ u3 = c.xos_orm.User.objects.get(id=orig_id)
+ assert(u3.password=="foobar")
+
+ # delete the user
+ u3.delete()
+
+ # make sure it is deleted
+ u_all = c.xos_orm.User.objects.all()
+ u_all = [x for x in u_all if x.email == u.email]
+ assert(len(u_all)==0)
+
+ print " okay"
+
+xos_grpc_client.start_api_parseargs(test_callback)
+
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index f0ef355..690fcf9 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -33,6 +33,7 @@
super(ORMWrapper, self).__setattr__("stub", stub)
super(ORMWrapper, self).__setattr__("cache", {})
super(ORMWrapper, self).__setattr__("reverse_cache", {})
+ super(ORMWrapper, self).__setattr__("poisoned", {})
super(ORMWrapper, self).__setattr__("is_new", is_new)
fkmap=self.gen_fkmap()
super(ORMWrapper, self).__setattr__("_fkmap", fkmap)
@@ -82,10 +83,29 @@
return self.cache[name]
+ def fk_set(self, name, model):
+ fk_entry = self._fkmap[name]
+ id = model.id
+ setattr(self._wrapped_class, fk_entry["src_fieldName"], id)
+
+ # XXX setting the cache here is a problematic, since the cached object's
+ # reverse foreign key pointers will not include the reference back
+ # to this object. Instead of setting the cache, let's poison the name
+ # and throw an exception if someone tries to get it.
+
+ # To work around this, explicitly call reset_cache(fieldName) and
+ # the ORM will reload the object.
+
+ self.poisoned[name] = True
+
def __getattr__(self, name, *args, **kwargs):
# note: getattr is only called for attributes that do not exist in
# self.__dict__
+ if name in self.poisoned.keys():
+ # see explanation in fk_set()
+ raise Exception("foreign key was poisoned")
+
if name in self._fkmap.keys():
return self.fk_resolve(name)
@@ -95,7 +115,9 @@
return getattr(self._wrapped_class, name, *args, **kwargs)
def __setattr__(self, name, value):
- if name in self.__dict__:
+ if name in self._fkmap.keys():
+ self.fk_set(name, value)
+ elif name in self.__dict__:
super(ORMWrapper,self).__setattr__(name, value)
else:
setattr(self._wrapped_class, name, value)
@@ -103,6 +125,19 @@
def __repr__(self):
return self._wrapped_class.__repr__()
+ def invalidate_cache(self, name=None):
+ if name:
+ if name in self.cache:
+ del self.cache[name]
+ if name in self.reverse_cache:
+ del self.reverse_cache[name]
+ if name in self.poisoned:
+ del self.poisoned[name]
+ else:
+ self.cache.clear()
+ self.reverse_cache.clear()
+ self.poisoned.clear()
+
def save(self):
if self.is_new:
new_class = self.stub.invoke("Create%s" % self._wrapped_class.__class__.__name__, self._wrapped_class)
diff --git a/xos/xos_client/xosapi/xos_grpc_client.py b/xos/xos_client/xosapi/xos_grpc_client.py
index 2b32914..f06aad5 100644
--- a/xos/xos_client/xosapi/xos_grpc_client.py
+++ b/xos/xos_client/xosapi/xos_grpc_client.py
@@ -1,3 +1,4 @@
+import argparse
import base64
import functools
import grpc
@@ -94,6 +95,116 @@
self.reconnect_callback2 = reconnect_callback
# -----------------------------------------------------------------------------
+# Wrappers for easy setup for test cases, etc
+# -----------------------------------------------------------------------------
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+
+ defs = {"grpc_insecure_endpoint": "xos-core.cord.lab:50055",
+ "grpc_secure_endpoint": "xos-core.cord.lab:50051",
+ "consul": None}
+
+ _help = '<hostname>:<port> to consul agent (default: %s)' % defs['consul']
+ parser.add_argument(
+ '-C', '--consul', dest='consul', action='store',
+ default=defs['consul'],
+ help=_help)
+
+ _help = ('gRPC insecure end-point to connect to. It can either be a direct'
+ 'definition in the form of <hostname>:<port>, or it can be an'
+ 'indirect definition in the form of @<service-name> where'
+ '<service-name> is the name of the grpc service as registered'
+ 'in consul (example: @voltha-grpc). (default: %s'
+ % defs['grpc_insecure_endpoint'])
+ parser.add_argument('-G', '--grpc-insecure-endpoint',
+ dest='grpc_insecure_endpoint',
+ action='store',
+ default=defs["grpc_insecure_endpoint"],
+ help=_help)
+
+ _help = ('gRPC secure end-point to connect to. It can either be a direct'
+ 'definition in the form of <hostname>:<port>, or it can be an'
+ 'indirect definition in the form of @<service-name> where'
+ '<service-name> is the name of the grpc service as registered'
+ 'in consul (example: @voltha-grpc). (default: %s'
+ % defs["grpc_secure_endpoint"])
+ parser.add_argument('-S', '--grpc-secure-endpoint',
+ dest='grpc_secure_endpoint',
+ action='store',
+ default=defs["grpc_secure_endpoint"],
+ help=_help)
+
+ parser.add_argument('-u', '--username',
+ dest='username',
+ action='store',
+ default=None,
+ help=_help)
+
+ parser.add_argument('-p', '--password',
+ dest='password',
+ action='store',
+ default=None,
+ help=_help)
+
+ _help = 'omit startup banner log lines'
+ parser.add_argument('-n', '--no-banner',
+ dest='no_banner',
+ action='store_true',
+ default=False,
+ help=_help)
+
+ _help = "suppress debug and info logs"
+ parser.add_argument('-q', '--quiet',
+ dest='quiet',
+ action='count',
+ help=_help)
+
+ _help = 'enable verbose logging'
+ parser.add_argument('-v', '--verbose',
+ dest='verbose',
+ action='count',
+ help=_help)
+
+ args = parser.parse_args()
+
+ return args
+
+def coreclient_reconnect(client, reconnect_callback, *args, **kwargs):
+ global coreapi
+
+ coreapi = coreclient.xos_orm
+
+ if reconnect_callback:
+ reconnect_callback(*args, **kwargs)
+
+ reactor.stop()
+
+def start_api(reconnect_callback, *args, **kwargs):
+ global coreclient
+
+ if kwargs.get("username", None):
+ coreclient = SecureClient(*args, **kwargs)
+ else:
+ coreclient = InsecureClient(*args, **kwargs)
+
+ coreclient.set_reconnect_callback(functools.partial(coreclient_reconnect, coreclient, reconnect_callback))
+ coreclient.start()
+
+ reactor.run()
+
+def start_api_parseargs(reconnect_callback):
+ args = parse_args()
+
+ if args.username:
+ start_api(reconnect_callback, endpoint=args.grpc_secure_endpoint, username=args.username, password=args.password)
+ else:
+ start_api(reconnect_callback, endpoint=args.grpc_insecure_endpoint)
+
+
+
+
+# -----------------------------------------------------------------------------
# Self test
# -----------------------------------------------------------------------------
diff --git a/xos/xos_client/xossh b/xos/xos_client/xossh
old mode 100644
new mode 100755