added SessionWP Manager

This commit is contained in:
root 2015-12-01 16:03:30 +01:00
parent 40518bf352
commit d869152323
11 changed files with 349 additions and 219 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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."""

View File

@ -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
)

View File

@ -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]

View File

@ -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)
'''

View File

@ -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')

View File

@ -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 */;

View File

@ -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: