def getNextRequest(factory, db): ''' Retrieve from DB request that has earliest time_requested and has state WAITING. Returns that request or None. ''' while True: with db: request = db.query_one('SELECT * \ FROM Requests \ WHERE time_requested = \ (SELECT MIN(time_requested) \ FROM Requests \ WHERE state = %s);', ('WAITING',)) if request is None: return None # Check if the time_requested already passed if request['time_requested'] < datetime.datetime.now(): with db: db.query_one('UPDATE Requests SET state = %s WHERE request_id = %s;', ('NOT_SENT', request['request_id'])) lu.writeToLog(factory, 'REQ# {} missed. New state: NOT SENT.') else: return request
def sendBidResults(self, request_id): ''' Collects bids for REQ# request_id and figures out what is the best bid. Then we proceed to send the best bid to the given drone/team. If this function is called it means at least one bid was submitted. ''' try: # Get bids bid and request from DB. with self.db: bids_accepted = self.db.query_list( 'SELECT * FROM Bids WHERE request_id = %s AND accepted = TRUE;', (request_id, )) if bids_accepted is None or len(bids_accepted) == 0: raise ('Failure collecting bids.') request = self.db.query_one( 'SELECT * FROM Requests WHERE request_id = %s', (request_id, )) if request is None: raise ('Failure collecting bids.') # Send result to winning bid best_bid = self.selectBestBid(bids_accepted) bid_winning_team = best_bid['team_id'] now = datetime.datetime.now() time_expected = now + datetime.timedelta( seconds=best_bid['seconds_expected']) with self.db: self.db.query_one( 'UPDATE Requests SET state = %s WHERE request_id = %s;', ('ASSIGNED', request_id)) self.db.query_one( 'UPDATE Requests SET time_assigned = %s WHERE request_id = %s;', (now, request_id)) self.db.query_one( 'UPDATE Requests SET time_expected = %s WHERE request_id = %s;', (time_expected, request_id)) tu.writeToTeam( self.teams[bid_winning_team].protocol, mt.WINNING_BID_RESULT(request, best_bid, str(time_expected))) # Send result to losing teams bid_losing_teams = [ bid['team_id'] for bid in bids_accepted if bid['team_id'] != bid_winning_team ] for blt in bid_losing_teams: protocol = self.teams[blt].protocol if protocol is not None: tu.writeToTeam(self.teams[blt].protocol, mt.LOSING_BID_RESULT(request)) except Exception as e: lu.writeToLog( self, '[ERROR] On sendBidResults. Error: {}'.format(str(e))) print(traceback.format_exc())
def stringReceived(self, line): ''' Called when line/message is received from team. Always check 'type' to decide what to do. Call methods from here but implement in MainFactory. ''' try: # Get message and log it. team_id = self.factory.protocols[self] message = line.decode() lu.writeToLogFromTeam(self.factory, message, team_id, verbose=True) # deserialize message message = json.loads(message) # Check if message has 'type' attribute if not su.hasattr(self, message, 'type'): return # Team Authentication if message['type'] == 'auth': tu.processAuthentication(self, message) return # Team is trying to send a message without our approval or before logging in. elif team_id is None or not self.factory.teams[team_id].isLoggedIn( ): tu.writeToTeam(self, mt.PLEASE_LOGIN_MSG) return # Team is logged in. We can accept data. else: # Team is sending drone state information, accept it and store it. if message['type'] == 'drone_state': self.updateDroneState(team_id, message) tu.writeToTeam(self, mt.THANKS_MSG, verbose=False) # Team want to log out. Log them out. elif message['type'] == 'logout': tu.logOutTeam(self.factory, team_id) elif message['type'] == 'bid': self.onReceiveBid(team_id, message) elif message['type'] == 'task_update': self.factory.onReceiveTaskUpdate(message) except Exception as e: lu.writeToLog(self.factory, '[ERROR] {}'.format(str(e)), verbose=False) print(traceback.format_exc())
def connectionLost(self, reason): ''' Called when connection to team is lost. Update relevant variables and clean up data structures so team can log in again. ''' # Log out team. team_id = self.factory.protocols[self] tu.logOutTeam(self.factory, team_id) # Lower number of protocols. self.factory.numProtocols -= 1 del self.factory.protocols[self] lu.writeToLog( self.factory, 'Connection lost. There are currently %d open connections.' % (self.factory.numProtocols, ))
def connectionMade(self): ''' Called when a conenction is made with a team for the first time. Update relevant variables and start authentication process. Note: self.factory was set by the factory's default buildProtocol ''' self.db = self.factory.db # Remember we work with protocols. Every protocol is a connection self.factory.protocols[self] = None self.factory.numProtocols += 1 lu.writeToLog( self.factory, 'Connection made. There are currently %d open connections.' % (self.factory.numProtocols, ))
def bidTimeOut(self, request_id): ''' Call when bid request times out. ''' try: lu.writeToLog(self, "[BID TIMEOUT] [REQ# {}]".format(request_id)) with self.db: # Check if someone accepted. If not, update state. result = self.db.query_one( 'SELECT state FROM Requests WHERE request_id = %s', (request_id, )) if result is not None: state = result[0] else: raise Exception('Failed to query request state.') if state == 'SENT': self.db.query_one( 'UPDATE Requests SET state = %s WHERE request_id = %s;', ('NOT_ACCEPTED', request_id)) lu.writeToLog( self, '[Request NOT_ACCEPTED] [REQ# {}]'.format(request_id)) return # If bid was accepted, collect bids. self.sendBidResults(request_id) except Exception as e: lu.writeToLog(self, '[ERROR] On bid timeout. Error: {}'.format(str(e))) print(traceback.format_exc())
def broadcastNextRequest(self, request): ''' Query request from DB and set a timer to broadcast the given request at the time specified in the request. ''' try: # Create request message for broadcast. request_msg = mt.REQUEST_MSG(request) # Send to all teams that are logged in and set timeout. lu.writeToLog( self, 'Brodcasting REQ# {} to Teams: {}'.format( request['request_id'], ','.join([ self.protocols[protocol] for protocol in self.protocols ]))) for protocol in self.protocols: tu.writeToTeam(protocol, request_msg, verbose=False) callId = reactor.callLater(REQUEST_TIMEOUT, self.bidTimeOut, request['request_id']) self.requestCallIds[request['request_id']] = callId # Update request state to sent. with self.db: self.db.query_one( 'UPDATE Requests SET state = %s WHERE request_id = %s;', ('SENT', request['request_id'])) sent_to = len(self.protocols) self.db.query_one( 'UPDATE Requests SET sent_to = %s WHERE request_id = %s;', (sent_to, request['request_id'])) self.startNextBroadcastTimer() except Exception as e: lu.writeToLog( self, '[ERROR] Failed broadcast requests. Error: {}'.format(str(e))) print(traceback.format_exc())
def startNextBroadcastTimer(self): try: # Retrieve request. request = su.getNextRequest(self, self.db) if request is None: lu.writeToLog(self, 'No WAITING requests left.') return # Start timer (by use of callback) time_til_broadcast = (request['time_requested'] - datetime.datetime.now()).seconds lu.writeToLog( self, 'Time until next broadcast: {}'.format(time_til_broadcast)) reactor.callLater(time_til_broadcast, self.broadcastNextRequest, (request)) except Exception as e: lu.writeToLog( self, '[ERROR] Failed at broadcastTimer. Error: {}'.format(str(e))) print(traceback.format_exc())
def onReceiveBid(self, team_id, message): ''' Handles bid reception from team. Checks request state, stores it in the database, updates request state. If all teams submit a bid for a request, stops timeout. Calls function to start sending the relevant task. ''' # Make sure message if formatted properly. if not su.hasattr(self, message, 'bid'): return bid = message['bid'] if not su.hasattrs(self, bid, atts.BID_ATTRS): return # Get request that corresponds to bid from DB and handle errors. request_id = bid['request_id'] try: with self.db: result = self.db.query_one( 'SELECT * FROM requests WHERE request_id = %s', (request_id, )) if result is not None: request = result else: raise Exception( '[ERROR ON RECEIVING BID] Key error [request_id = {}]'. format(request_id)) # Case when bid already timed out if request['state'] != 'SENT' and request['state'] != 'ACCEPTED': raise Exception( "[ERROR ON RECEIVING BID] Already timeout [TEAM# {1}, REQ# {0}]" .format(team_id, request_id)) # Try to put into DB and update request_states with self.db: bid_exists = self.db.count('Bids', 'request_id = %s AND team_id = %s', (request_id, team_id)) if bid_exists: raise Exception( '[ERROR ON RECEIVING BID] Bid already exists [TEAM# {1}, REQ# {0}]' .format(request_id, team_id)) else: bid_accepted = True if bid['accepted'] else False self.db.insert_values( 'Bids', (DBHandler.DEFAULT, bid['price'], bid['seconds_expected'], bid['drone_id'], bid_accepted, False, team_id, request_id)) # count number of bids n_bids = self.db.count('Bids', 'request_id = %s', (request_id, )) result = self.db.query_one( 'SELECT sent_to FROM Requests WHERE request_id = %s', (request_id, )) if result is not None: n_sent = result[0] else: raise Exception( '[ERROR ON RECEIVING BID] [TEAM# {1}, REQ# {0}]') tu.writeToTeam(self, mt.THANKS_MSG, verbose=False) # Check if all teams submitted bids to stop timeout and send results. if n_bids == n_sent: with self.db: self.db.query_one( 'UPDATE Requests SET state = %s WHERE request_id = %s; ', ('ALL_ACCEPTED', request_id)) lu.writeToLog( self.factory, "Received ALL bids for REQ# {}, timeout callback canceled." .format(request_id)) self.factory.requestCallIds[request_id].cancel() self.factory.sendBidResults(request_id) else: with self.db: self.db.query_one( 'UPDATE Requests SET state = %s WHERE request_id = %s;', ('ACCEPTED', request_id)) except Exception as e: lu.writeToLog(self.factory, str(e)) tu.writeToTeam( self, mt.ERROR_RESPONSE('Bidding error. Error: {}'.format(str(e)))) print(traceback.format_exc())
def logOutTeam(factory, team_id): if team_id is not None: factory.teams[team_id].logOut() lu.writeToLog(factory, 'Team ' + team_id + ' logged out.')