From d869152323f6b606c4c7d7e4dfbfdba898cc71eb Mon Sep 17 00:00:00 2001 From: root Date: Tue, 1 Dec 2015 16:03:30 +0100 Subject: [PATCH] added SessionWP Manager --- bin/iotronic | 2 +- iotronic/api/controllers/v1/node.py | 8 +- iotronic/db/api.py | 27 ++++ iotronic/db/sqlalchemy/api.py | 222 +++++----------------------- iotronic/db/sqlalchemy/models.py | 26 +++- iotronic/objects/__init__.py | 3 + iotronic/objects/sessionwp.py | 208 ++++++++++++++++++++++++++ iotronic/wamp/functions.py | 48 ++++-- iotronic/wamp/rpcwampserver.py | 12 +- utils/dump.sql | 11 +- utils/rpc_test.py | 1 + 11 files changed, 349 insertions(+), 219 deletions(-) create mode 100644 iotronic/objects/sessionwp.py diff --git a/bin/iotronic b/bin/iotronic index f5fcb8e..de2b0cd 100755 --- a/bin/iotronic +++ b/bin/iotronic @@ -16,7 +16,7 @@ list) curl -sS $BASE/nodes/ | python -m json.tool echo ""; ;; create-node) curl -sS -H "Content-Type: application/json" -X POST $BASE/nodes/ \ --d '{"code":"'"$2"'","name":"'"$3"'","location":[{"latitude":"'"$4"'","longitude":"'"$5"'","altitude":"'"$6"'"}]}' | python -m json.tool +-d '{"device":"arduino","code":"'"$2"'","name":"'"$3"'","location":[{"latitude":"'"$4"'","longitude":"'"$5"'","altitude":"'"$6"'"}]}' | python -m json.tool echo ""; ;; delete-node) curl -sS -X DELETE $BASE/nodes/$2 | python -m json.tool diff --git a/iotronic/api/controllers/v1/node.py b/iotronic/api/controllers/v1/node.py index e220761..d75189c 100644 --- a/iotronic/api/controllers/v1/node.py +++ b/iotronic/api/controllers/v1/node.py @@ -31,8 +31,14 @@ class Node(base.APIBase): @staticmethod def _convert_with_locates(node, url, expand=True, show_password=True): + try: + session=objects.SessionWP({}).get_session_by_node_uuid(node.uuid,valid=True) + node.session=session.session_id + except: + pass + if not expand: - except_list = ['name', 'code', 'status','uuid'] + except_list = ['name', 'code', 'status','uuid','session'] node.unset_fields_except(except_list) return node diff --git a/iotronic/db/api.py b/iotronic/db/api.py index d719e3e..f7044c1 100644 --- a/iotronic/db/api.py +++ b/iotronic/db/api.py @@ -408,13 +408,40 @@ class Connection(object): ###################### NEW ############################# + + @abc.abstractmethod + def create_session(self, values): + """Create a new location. + + :param values: session_id. + """ + + @abc.abstractmethod + def update_session(self, session_id, values): + """Update properties of an session. + + :param session_id: The id of a session. + :param values: Dict of values to update. + :returns: A session. + """ + @abc.abstractmethod def create_location(self, values): """Create a new location. :param values: Dict of values. + """ + + @abc.abstractmethod + def update_location(self, location_id, values): + """Update properties of an location. + + :param location_id: The id of a location. + :param values: Dict of values to update. + :returns: A location. """ + @abc.abstractmethod def destroy_location(self, location_id): """Destroy an location. diff --git a/iotronic/db/sqlalchemy/api.py b/iotronic/db/sqlalchemy/api.py index 6287535..f39a3b5 100644 --- a/iotronic/db/sqlalchemy/api.py +++ b/iotronic/db/sqlalchemy/api.py @@ -159,7 +159,6 @@ def add_location_filter_by_node(query, value): models.Location.node_id == models.Node.id) return query.filter(models.Node.uuid == value) - class Connection(api.Connection): """SqlAlchemy connection.""" @@ -667,6 +666,24 @@ class Connection(api.Connection): ###################### NEW ############################# + def create_session(self, values): + session = models.SessionWP() + session.update(values) + session.save() + return session + + def update_session(self, ses_id, values): + # NOTE(dtantsur): this can lead to very strange errors + session = get_session() + try: + with session.begin(): + query = model_query(models.SessionWP, session=session) + query = add_identity_filter(query, ses_id) + ref = query.one() + ref.update(values) + except NoResultFound: + raise exception.SessionWPNotFound(ses=ses_id) + return ref def create_location(self, values): location = models.Location() @@ -674,11 +691,24 @@ class Connection(api.Connection): location.save() return location + def update_location(self, location_id, values): + # NOTE(dtantsur): this can lead to very strange errors + session = get_session() + try: + with session.begin(): + query = model_query(models.Location, session=session) + query = add_identity_filter(query, location_id) + ref = query.one() + ref.update(values) + except NoResultFound: + raise exception.LocationNotFound(location=location_id) + return ref + def destroy_location(self, location_id): session = get_session() with session.begin(): query = model_query(models.Location, session=session) - query = add_location_filter(query, location_id) + query = add_identity_filter(query, location_id) count = query.delete() if count == 0: raise exception.LocationNotFound(location=location_id) @@ -690,192 +720,16 @@ class Connection(api.Connection): return _paginate_query(models.Location, limit, marker, sort_key, sort_dir, query) -""" - def _add_boards_filters(self, query, filters): - if filters is None: - filters = [] - - if 'chassis_uuid' in filters: - # get_chassis_by_uuid() to raise an exception if the chassis - # is not found - chassis_obj = self.get_chassis_by_uuid(filters['chassis_uuid']) - query = query.filter_by(chassis_id=chassis_obj.id) - if 'associated' in filters: - if filters['associated']: - query = query.filter(models.Board.instance_uuid != None) - else: - query = query.filter(models.Board.instance_uuid == None) - if 'reserved' in filters: - if filters['reserved']: - query = query.filter(models.Board.reservation != None) - else: - query = query.filter(models.Board.reservation == None) - if 'maintenance' in filters: - query = query.filter_by(maintenance=filters['maintenance']) - if 'driver' in filters: - query = query.filter_by(driver=filters['driver']) - if 'provision_state' in filters: - query = query.filter_by(provision_state=filters['provision_state']) - if 'provisioned_before' in filters: - limit = (timeutils.utcnow() - - datetime.timedelta(seconds=filters['provisioned_before'])) - query = query.filter(models.Board.provision_updated_at < limit) - if 'inspection_started_before' in filters: - limit = ((timeutils.utcnow()) - - (datetime.timedelta( - seconds=filters['inspection_started_before']))) - query = query.filter(models.Board.inspection_started_at < limit) - - return query - - def get_board_by_uuid(self, board_uuid): - query = model_query(models.Board).filter_by(uuid=board_uuid) + def get_session_by_node_uuid(self, node_uuid, valid): + query = model_query(models.SessionWP).filter_by(node_uuid=node_uuid).filter_by(valid=valid) try: return query.one() except NoResultFound: - raise exception.NodeNotFound(node=board_uuid) - - def get_board_list(self, filters=None, limit=None, marker=None, - sort_key=None, sort_dir=None): - query = model_query(models.Board) - query = self._add_boards_filters(query, filters) - return _paginate_query(models.Board, limit, marker, - sort_key, sort_dir, query) - - def reserve_board(self, tag, board_id): - session = get_session() - with session.begin(): - query = model_query(models.Board, session=session) - query = add_identity_filter(query, board_id) - # be optimistic and assume we usually create a reservation - count = query.filter_by(reservation=None).update( - {'reservation': tag}, synchronize_session=False) - try: - board = query.one() - if count != 1: - # Nothing updated and board exists. Must already be - # locked. - raise exception.BoardLocked(board=board_id, - host=board['reservation']) - return board - except NoResultFound: - raise exception.BoardNotFound(board_id) - - def release_board(self, tag, board_id): - session = get_session() - with session.begin(): - query = model_query(models.Board, session=session) - query = add_identity_filter(query, board_id) - # be optimistic and assume we usually release a reservation - count = query.filter_by(reservation=tag).update( - {'reservation': None}, synchronize_session=False) - try: - if count != 1: - board = query.one() - if board['reservation'] is None: - raise exception.BoardNotLocked(board=board_id) - else: - raise exception.BoardLocked(board=board_id, - host=board['reservation']) - except NoResultFound: - raise exception.BoardNotFound(board_id) - - def destroy_board(self, board_id): - session = get_session() - with session.begin(): - query = model_query(models.Board, session=session) - query = add_identity_filter(query, board_id) - - try: - board_ref = query.one() - except NoResultFound: - raise exception.BoardNotFound(board=board_id) - - # Get board ID, if an UUID was supplied. The ID is - # required for deleting all ports, attached to the board. - if uuidutils.is_uuid_like(board_id): - board_id = board_ref['id'] - - query.delete() - - - def get_boardinfo_list(self, columns=None, filters=None, limit=None, - marker=None, sort_key=None, sort_dir=None): - # list-ify columns default values because it is bad form - # to include a mutable list in function definitions. - if columns is None: - columns = [models.Board.id] - else: - columns = [getattr(models.Board, c) for c in columns] - - query = model_query(*columns, base_model=models.Board) - query = self._add_boards_filters(query, filters) - return _paginate_query(models.Board, limit, marker, - sort_key, sort_dir, query) - - def get_board_list(self, filters=None, limit=None, marker=None, - sort_key=None, sort_dir=None): - query = model_query(models.Board) - query = self._add_boards_filters(query, filters) - return _paginate_query(models.Board, limit, marker, - sort_key, sort_dir, query) + return None - def get_board_by_code(self, board_name): - query = model_query(models.Board).filter_by(code=board_name) + def get_session_by_session_id(self, session_id): + query = model_query(models.SessionWP).filter_by(session_id=session_id) try: return query.one() except NoResultFound: - raise exception.BoardNotFound(board=board_name) - - def update_board(self, board_id, values): - if 'uuid' in values: - msg = _("Cannot overwrite UUID for an existing Board.") - raise exception.InvalidParameterValue(err=msg) - - try: - return self._do_update_board(board_id, values) - except db_exc.DBDuplicateEntry as e: - if 'code' in e.columns: - raise exception.DuplicateName(name=values['name']) - elif 'uuid' in e.columns: - raise exception.BoardAlreadyExists(uuid=values['uuid']) - ''' - elif 'instance_uuid' in e.columns: - raise exception.InstanceAssociated( - instance_uuid=values['instance_uuid'], - board=board_id) - ''' - else: - raise e - - def _do_update_board(self, board_id, values): - session = get_session() - with session.begin(): - query = model_query(models.Board, session=session) - query = add_identity_filter(query, board_id) - try: - ref = query.with_lockmode('update').one() - except NoResultFound: - raise exception.BoardNotFound(board=board_id) - ''' - # Prevent instance_uuid overwriting - if values.get("instance_uuid") and ref.instance_uuid: - raise exception.BoardAssociated( - board=board_id, instance=ref.instance_uuid) - - if 'provision_state' in values: - values['provision_updated_at'] = timeutils.utcnow() - if values['provision_state'] == states.INSPECTING: - values['inspection_started_at'] = timeutils.utcnow() - values['inspection_finished_at'] = None - elif (ref.provision_state == states.INSPECTING and - values['provision_state'] == states.MANAGEABLE): - values['inspection_finished_at'] = timeutils.utcnow() - values['inspection_started_at'] = None - elif (ref.provision_state == states.INSPECTING and - values['provision_state'] == states.INSPECTFAIL): - values['inspection_started_at'] = None - ''' - ref.update(values) - return ref -""" + return None \ No newline at end of file diff --git a/iotronic/db/sqlalchemy/models.py b/iotronic/db/sqlalchemy/models.py index 9b5b7ae..81a48fa 100644 --- a/iotronic/db/sqlalchemy/models.py +++ b/iotronic/db/sqlalchemy/models.py @@ -152,7 +152,7 @@ class Node(Base): code = Column(String(25)) status = Column(String(15), nullable=True) name = Column(String(255), nullable=True) - device = Column(String(255), nullable=True) + device = Column(String(255)) session = Column(String(255), nullable=True) mobile = Column(Boolean, default=False) #location = Column(JSONEncodedDict) @@ -213,16 +213,30 @@ class Node(Base): """ class Location(Base): - """Represents a network port of a bare metal node.""" + """Represents a location of a node.""" __tablename__ = 'locations' __table_args__ = ( table_args()) id = Column(Integer, primary_key=True) - longitude = Column(String(18)) - latitude = Column(String(18)) - altitude = Column(String(18)) - node_id = Column(Integer, ForeignKey('nodes.id'), nullable=True) + longitude = Column(String(18), nullable=True) + latitude = Column(String(18), nullable=True) + altitude = Column(String(18), nullable=True) + node_id = Column(Integer, ForeignKey('nodes.id')) + +class SessionWP(Base): + """Represents a session of a node.""" + + __tablename__ = 'sessions' + __table_args__ = ( + schema.UniqueConstraint('session_id', name='uniq_session_id0session_id'), + schema.UniqueConstraint('node_uuid', name='uniq_node_uuid0node_uuid'), + table_args()) + id = Column(Integer, primary_key=True) + valid = Column(Boolean, default=True) + session_id = Column(String(15)) + node_uuid = Column(String(36)) + node_id = Column(Integer, ForeignKey('nodes.id')) class Port(Base): """Represents a network port of a bare metal node.""" diff --git a/iotronic/objects/__init__.py b/iotronic/objects/__init__.py index 3f137f1..1e66db4 100644 --- a/iotronic/objects/__init__.py +++ b/iotronic/objects/__init__.py @@ -16,6 +16,7 @@ from iotronic.objects import conductor from iotronic.objects import node from iotronic.objects import location +from iotronic.objects import sessionwp #from iotronic.objects import port @@ -23,6 +24,7 @@ from iotronic.objects import location Conductor = conductor.Conductor Node = node.Node Location = location.Location +SessionWP=sessionwp.SessionWP #Port = port.Port __all__ = ( @@ -30,5 +32,6 @@ __all__ = ( Conductor, Node, Location, + SessionWP, #Port ) diff --git a/iotronic/objects/sessionwp.py b/iotronic/objects/sessionwp.py new file mode 100644 index 0000000..b2b66e0 --- /dev/null +++ b/iotronic/objects/sessionwp.py @@ -0,0 +1,208 @@ +# coding=utf-8 +# +# +# 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 oslo_utils import strutils +from oslo_utils import uuidutils + +from iotronic.common import exception +from iotronic.common import utils +from iotronic.db import api as dbapi +from iotronic.objects import base +from iotronic.objects import utils as obj_utils + + +class SessionWP(base.IotronicObject): + VERSION = '1.0' + + dbapi = dbapi.get_instance() + + fields = { + 'id': int, + 'node_uuid': obj_utils.str_or_none, + 'session_id': obj_utils.str_or_none, + 'node_id': obj_utils.int_or_none, + 'valid': bool, + } + + @staticmethod + def _from_db_object(session, db_session): + """Converts a database entity to a formal object.""" + for field in session.fields: + session[field] = db_session[field] + + session.obj_reset_changes() + return session + + @staticmethod + def _from_db_object_list(db_objects, cls, context): + """Converts a list of database entities to a list of formal objects.""" + return [SessionWP._from_db_object(cls(context), obj) for obj in db_objects] + + @base.remotable_classmethod + def get(cls, context, session_id): + """Find a session based on its id or uuid and return a SessionWP object. + + :param session_id: the id *or* uuid of a session. + :returns: a :class:`SessionWP` object. + """ + if strutils.is_int_like(session_id): + return cls.get_by_id(context, session_id) + elif uuidutils.is_uuid_like(session_id): + return cls.get_by_uuid(context, session_id) + else: + raise exception.InvalidIdentity(identity=session_id) + + @base.remotable_classmethod + def get_by_id(cls, context, ses_id): + """Find a session based on its integer id and return a SessionWP object. + + :param ses_id: the id of a session. + :returns: a :class:`SessionWP` object. + """ + db_session = cls.dbapi.get_session_by_id(ses_id) + session = SessionWP._from_db_object(cls(context), db_session) + return session + + @base.remotable_classmethod + def get_by_session_id(cls, context, session_id): + """Find a session based on its integer id and return a SessionWP object. + + :param session_id: the id of a session. + :returns: a :class:`SessionWP` object. + """ + db_session = cls.dbapi.get_session_by_session_id(session_id) + session = SessionWP._from_db_object(cls(context), db_session) + return session + + @base.remotable_classmethod + def get_session_by_node_uuid(cls,node_uuid,valid=True, context=None ): + """Find a session based on uuid and return a :class:`SessionWP` object. + + :param node_uuid: the uuid of a node. + :param context: Security context + :returns: a :class:`SessionWP` object. + """ + db_session = cls.dbapi.get_session_by_node_uuid(node_uuid,valid) + session = SessionWP._from_db_object(cls(context), db_session) + return session + + @base.remotable_classmethod + def list(cls, context, limit=None, marker=None, + sort_key=None, sort_dir=None): + """Return a list of SessionWP objects. + + :param context: Security context. + :param limit: maximum number of resources to return in a single result. + :param marker: pagination marker for large data sets. + :param sort_key: column to sort results by. + :param sort_dir: direction to sort. "asc" or "desc". + :returns: a list of :class:`SessionWP` object. + + """ + db_sessions = cls.dbapi.get_session_list(limit=limit, + marker=marker, + sort_key=sort_key, + sort_dir=sort_dir) + return SessionWP._from_db_object_list(db_sessions, cls, context) + + @base.remotable_classmethod + def list_by_node_id(cls, context, node_id, limit=None, marker=None, + sort_key=None, sort_dir=None): + """Return a list of SessionWP objects associated with a given node ID. + + :param context: Security context. + :param node_id: the ID of the node. + :param limit: maximum number of resources to return in a single result. + :param marker: pagination marker for large data sets. + :param sort_key: column to sort results by. + :param sort_dir: direction to sort. "asc" or "desc". + :returns: a list of :class:`SessionWP` object. + + """ + db_sessions = cls.dbapi.get_sessions_by_node_id(node_id, limit=limit, + marker=marker, + sort_key=sort_key, + sort_dir=sort_dir) + return SessionWP._from_db_object_list(db_sessions, cls, context) + + @base.remotable + def create(self,context=None): + """Create a SessionWP record in the DB. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: SessionWP(context) + + """ + values = self.obj_get_changes() + db_session = self.dbapi.create_session(values) + self._from_db_object(self, db_session) + + @base.remotable + def destroy(self, context=None): + """Delete the SessionWP from the DB. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: SessionWP(context) + """ + self.dbapi.destroy_session(self.uuid) + self.obj_reset_changes() + + @base.remotable + def save(self, context=None): + """Save updates to this SessionWP. + + Updates will be made column by column based on the result + of self.what_changed(). + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: SessionWP(context) + """ + updates = self.obj_get_changes() + self.dbapi.update_session(self.id, updates) + + self.obj_reset_changes() + + @base.remotable + def refresh(self, context=None): + """Loads updates for this SessionWP. + + Loads a session with the same uuid from the database and + checks for updated attributes. Updates are applied from + the loaded session column by column, if there are any updates. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: SessionWP(context) + """ + current = self.__class__.get_by_uuid(self._context, uuid=self.uuid) + for field in self.fields: + if (hasattr(self, base.get_attrname(field)) and + self[field] != current[field]): + self[field] = current[field] diff --git a/iotronic/wamp/functions.py b/iotronic/wamp/functions.py index 0138eb1..9b21c12 100644 --- a/iotronic/wamp/functions.py +++ b/iotronic/wamp/functions.py @@ -2,29 +2,45 @@ from iotronic import objects from oslo_utils import uuidutils import pecan from oslo_log import log +from iotronic.common import exception LOG = log.getLogger(__name__) +def leave_function(session_id): + LOG.debug('Node with %s disconnectd',session_id) + try: + old_session=objects.SessionWP({}).get_by_session_id({},session_id) + old_session.valid=False + old_session.save() + LOG.debug('Session %s deleted', session_id) + except: + LOG.debug('Error in deleting session %s', session_id) + + def test(): LOG.debug('hellooooooooo') return u'hello!' -def registration(code,session_num): +def registration(code_node,session_num): + response='' + try: + node = objects.Node.get_by_code({}, code_node) + except: + response = exception.NodeNotFound(node=code_node) + try: + old_session=objects.SessionWP({}).get_session_by_node_uuid(node.uuid,valid=True) + old_session.valid=False + old_session.save() + except: + LOG.debug('valid session for %s Not found', node.uuid) - - board = objects.Board.get_by_code({}, code) - if not board: - new_Board = objects.Board({}) - new_Board.uuid=uuidutils.generate_uuid() - new_Board.code=str(code) - new_Board.status='CONNECTED' + session=objects.SessionWP({}) + session.node_id=node.id + session.node_uuid=node.uuid + session.session_id=session_num + session.create() + session.save() - new_Board.create() - response='NO BOARD FOUND, inserted new board: '+str(code) - return unicode(response) + return unicode(response) - board.status='CONNECTED' - board.save() - - response='Board '+ code +' connected' - return unicode(response) \ No newline at end of file + ''' \ No newline at end of file diff --git a/iotronic/wamp/rpcwampserver.py b/iotronic/wamp/rpcwampserver.py index 128a941..f2730e3 100644 --- a/iotronic/wamp/rpcwampserver.py +++ b/iotronic/wamp/rpcwampserver.py @@ -21,11 +21,11 @@ class RPCWampManager(ApplicationSession): def onChallenge(self, challenge): print("authentication challenge received") - #def onLeave(self, details): - # print("session left") - # import os, signal - # os.kill(multi.pid, signal.SIGKILL) - + def onLeave(self, details): + print("session left") + import os, signal + os.kill(multi.pid, signal.SIGKILL) + def onDisconnect(self): print("transport disconnected") ''' @@ -34,6 +34,8 @@ class RPCWampManager(ApplicationSession): def onJoin(self, details): LOG.info('RPC Wamp Session ready') import iotronic.wamp.functions as fun + self.subscribe(fun.leave_function, 'wamp.session.on_leave') + try: yield self.register(fun.test, u'stack4things.conductor.rpc.test') yield self.register(fun.registration, u'stack4things.conductor.rpc.registration') diff --git a/utils/dump.sql b/utils/dump.sql index e9f5ec7..75d734f 100644 --- a/utils/dump.sql +++ b/utils/dump.sql @@ -36,7 +36,7 @@ CREATE TABLE `nodes` ( `name` varchar(255) DEFAULT NULL, `device` varchar(255) NOT NULL, `session` varchar(255) DEFAULT NULL, - `mobile` tinyint(1) DEFAULT 0, + `mobile` tinyint(1) DEFAULT 0 NOT NULL, `extra` text, PRIMARY KEY (`id`), UNIQUE KEY `uuid` (`uuid`), @@ -51,14 +51,13 @@ CREATE TABLE `sessions` ( `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, `id` int(11) NOT NULL AUTO_INCREMENT, - `valid` tinyint(1) DEFAULT NULL, - `session_id` varchar(18) DEFAULT NULL, - `node_id` int(11) NOT NULL, + `valid` tinyint(1) DEFAULT 1 NOT NULL, + `session_id` varchar(18) NOT NULL, `node_uuid` varchar(36) NOT NULL, + `node_id` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `session_id` (`session_id`), - CONSTRAINT `session_node_id` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id`), - CONSTRAINT `session_node_uuid` FOREIGN KEY (`node_uuid`) REFERENCES `nodes` (`uuid`) + CONSTRAINT `session_node_id` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/utils/rpc_test.py b/utils/rpc_test.py index 1852e67..0004143 100644 --- a/utils/rpc_test.py +++ b/utils/rpc_test.py @@ -8,6 +8,7 @@ class RPCCaller(ApplicationSession): def onJoin(self, details): print("session ready") try: + #stack4things.iotronic.conductor.function res = yield self.call(u'stack4things.conductor.rpc.test',) print("call result: {}".format(res)) except Exception as e: