def __init__(self, priority=False, monitorQ=False, \ unitName="packet", capacity="unbounded", \ initialBuffered=None, **kwargs): """Constructor. :param priority: Boolean; if True, use priority queueing. :param monitorQ: Boolean; if True, support `enQ`, `deQ`, and `drp`. :param unitName: Description of units stored in `Queue`. :param capacity: Capacity of underlying Store [default='unbounded']. :param initialBuffered: Initialize list of buffered objects. :param kwargs: Keywords passed to `Traceable` constructors. """ # check that initialBuffered is properly formatted if initialBuffered and priority: isPrio = all([isinstance(s,tuple) for s in initialBuffered] ) if not isPrio: initialBuffered = [(p, 0) for p in initialBuffered] # call constructors Store.__init__(self, unitName=unitName, capacity=capacity, \ initialBuffered=initialBuffered, \ putQType=PriorityQ, getQType=PriorityQ) Traceable.__init__(self, **kwargs) # set other parameters self.tracename = self.tracename + "%d"%(self.uid) self.__priority = priority self.monitorQ = monitorQ self.enQ = SimEvent(name=self.name+".enQ") self.deQ = SimEvent(name=self.name+".deQ") self.drp = SimEvent(name=self.name+".drp") self.__dummy = SimEvent(name=self.name+".dummy") # set up Queue for priority if self.priority: self.theBuffer = _PriorityList(self.theBuffer) self.addSort(None) # setup addSort for priority queueing
def __init__(self): Process.__init__(self) self._signals = { 'kill' : SimEvent('kill'), 'finish' : SimEvent('finish'), } self._finished = False
def __init__(self, dataaccess, discovery, sim): super(Provider, self).__init__(sim = sim) self.discovery = discovery self.discovery.add_changes_observer(self) if dataaccess is not None: # it should be None just for testing purposes! self.clue_manager = ClueManager(dataaccess) self.__stop = False self.wp_node_name = None self.connector = None self.externalCondition = SimEvent(name="external_condition_on_%s"%(self.name), sim = sim) self.on_external_condition = False # both when a response from a request is received or when the WP changes! self.clueChanged = SimEvent(name="clue_change_on_%s"%(self.name), sim = sim) self.stopProvider = SimEvent(name="stop_provider_%s"%(self.name), sim = sim) self.timer = None self.last_contribution_to_aggregated_clue = Version(-1, -1) self.last_wp_notification = WPRequestNotifier("super_fake_node", self.sim) # When we have to detect the need of updating our clues in the WP: # have we received a new version confirming that the WP received it correctly? # Once we decide to send our clue a WP needs to acknowledge that has received it. # Even if it is not the same WP. # Otherwise it may happen that a new WP is initialized from a version higher than # the one this node has contributed without having its information. self.pending_update = False
def __init__(self, **kwargs): """Constructor.""" self.__csmode = CSPHY_IDLE self.csbusy = SimEvent(name="csbusy") self.csidle = SimEvent(name="csidle") PHY.__init__(self, **kwargs) # rename events self.csbusy.name = "%s(%s).csbusy"%(self.name, self.uid) self.csidle.name = "%s(%s).csidle"%(self.name, self.uid) # monitor events -> keep up to date monitor_events(self.csbusy, self.csidle)
class Timer(Process): def __init__(self, waitUntil=10000.0, name="timer", sim=None): super(Timer, self).__init__(name=name, sim=sim) self.__timeout = waitUntil self.event = SimEvent(name="timer_event", sim=sim) self.ended = False def wait(self): yield hold, self, self.__timeout self.ended = True self.event.signal()
class ContinuousQuerier(Process): """Check Section 5.2""" # the interval between the first two queries MUST be at least one second MINIMUM_FIRST_WAIT = 1000 MINIMUM_INCREMENT_RATE = 2 FIRST_WAIT = MINIMUM_FIRST_WAIT + 4000 INCREMENT_RATE = MINIMUM_INCREMENT_RATE * 2 def __init__(self, subquery, sim, sender=None): super(ContinuousQuerier, self).__init__(sim=sim) self.sender = sender # if there is a unique response, it should stop querying # I will assume that there is not unique response (asking for PTR records) self.subquery = subquery self._random = Random() self.stopped = False self.__stop = SimEvent(name="stop_continuous_querier", sim=sim) def query_continuously(self): # SHOULD also delay the first query of the series # by a randomly-chosen amount in the range 20-120ms. twait = 20 + self._random.random()*100 timer = Timer(waitUntil=twait, sim=self.sim) self.sim.activate(timer, timer.wait()) yield waitevent, self, (timer.event, self.__stop,) self.sender.send_query(self.subquery, to_node=self.sender.node_id) # joining a network # the interval between the first two queries MUST be at least one second twait = ContinuousQuerier.FIRST_WAIT while not self.stopped: timer = Timer(waitUntil=twait, sim=self.sim) self.sim.activate(timer, timer.wait()) yield waitevent, self, (timer.event, self.__stop,) self.sender.send_query(self.subquery) # subsequent queries if twait!=3600000: # the intervals between successive queries MUST increase by at least a factor of two twait = twait * ContinuousQuerier.INCREMENT_RATE # When the interval between queries reaches or exceeds 60 minutes # a querier MAY cap the interval to a maximum of 60 minutes # and perform subsequent queries at a steady-state rate of one query per hour if twait>3600000: twait = 3600000 # 1h def stop(self): self.stopped = True self.__stop.signal() def reset(self): self.stopped = False
def __init__(self, *args, **kwargs): """Constructor.""" # set up parameters self.__mac = None self.rreqrate = None self.datarate = None self.maintbuffer = None # additional parameters for signalling self.sndrreq = SimEvent() self.drprreq = SimEvent() self.finrreq = SimEvent() self.sndfail = SimEvent() # call base constructor Routing.__init__(self, *args, **kwargs)
class Alarm(Process): @classmethod def setOnetime(cls, delay, name=None, at=0, drift=('fixed', 0)): tm = Alarm(delay, name, drift=drift) activate(tm, tm.onetime()) return tm.event @classmethod def setPeriodic(cls, interval, name=None, at=0, until=infinite, drift=('fixed', 0)): tm = Alarm(interval, name, until, drift) activate(tm, tm.loop(), at=at) return tm.event def __init__(self, interval, name=None, until=infinite, drift=('fixed', 0)): Process.__init__(self) self.interval = interval if name is not None: eventname = name else: eventname = "a_SimEvent" self.event = SimEvent(eventname) self.until = until try: key, mean, cfg = drift except ValueError: key, mean = drift cfg = {} lb = cfg.get('lb', 0); ub = cfg.get('ub', interval) if lb < 0: raise ValueError('drift lb = %s >= 0' %lb) if ub > interval: raise ValueError('drift ub = %s < %s = interval' %interval) cfg['lb'] = lb; cfg['ub'] = ub self.rgen = RandInterval.get(key, mean, cfg) def onetime(self): drift = self.rgen.next() yield hold, self, self.interval + drift self.event.signal() def loop(self): left = 0 while (self.until < 0) or (now() < self.until): yield hold, self, left drift = self.rgen.next() yield hold, self, drift self.event.signal() left = self.interval - drift
def __init__(self, cnode, index, configs): StorageNode.__init__(self, cnode, index, configs) #txns that are not yet granted locks, in FCFS order self.lockingQueue = [] self.waitingSet = set([]) self.nextEvent = SimEvent() self.ts = 0
def __init__(self, sim, sender=None): super(Responder, self).__init__(sim=sim) self._random = Random() self.__new_query_queued = SimEvent(name="new_query_queued", sim=sim) self.local_records = {} # key: record, value: last time advertised self.queued_queries = [] # tuple: ( when to answer, query ) self.sender = sender
def __init__(self, cwmin=None, cwmax=None, retrylimit=None, \ usecsma=False, **kwargs): """Constructor. :param cwmin: Minimum contention window size. :param cwmax: Maximum contention window size. :param retrylimit: Maximum number of retries allowed. :param usecsma: Boolean flag; if true, use CSMA/CA without RTS-CTS reservation messages. :param kwargs: Additional keywords passed to `configure()`. The default parameters are specified by the class. """ if cwmin is None: cwmin = self.__class__.cwmin if cwmax is None: cwmax = self.__class__.cwmax if retrylimit is None: retrylimit = self.__class__.retrylimit # timing parameters self.sifs, self.slottime = None, None self.cwmin, self.cwmax = cwmin, cwmax self.cslot = None # events and other members self.datatosend = None self.retrycount = None self.retrylimit = retrylimit self.usecsma = usecsma self.rxdata = SimEvent(name=".rxdata") self._ctsduration = None self._ackduration = None # call CSMAC constructor CSMAC.__init__(self, **kwargs) self.rxdata.name = "%s.rxdata"%(self.name)
def __init__(self, name, sim, task): Process.__init__(self, name=name, sim=sim) # number of the action self.q = 0 # corresonding pycpa task self.task = task # the signal used to wake up the activation event self.signal_event = SimEvent(sim=sim) # workload left to consume self.workload = task.wcet # active segments of the execution of the form: [(0,1), (3,9)] self.exec_windows = list() # last recent start of a execution segment self.recent_window_start = None # actual response time self.response_time = 0 # start time self.start_time = 0 # finishing time self.finish_time = 0
def __init__(self, system, ID, configs): IDable.__init__(self, 'zone%s/cn' % ID) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = system self.snodes = [] self.configs = configs self.groupLocations = {} self.txnsRunning = set([]) self.shouldClose = False self.closeEvent = SimEvent() #paxos entities self.paxosPRunner = None self.paxosAcceptor = None self.paxosLearner = None
def configure(self, **kwargs): """Initialize `timer` to None and create monitor.""" self.__timer = None self.__wakeup = SimEvent(name=self.name+".wake") mon = self.newchild("mon", FSM, tracename=self.tracename+".MON") #mon.goto(self.MON) mon.goto(self.MONIDLE)
def __init__(self, sim, record_observer=None): super(Cache, self).__init__(sim=sim) self.record_observer = record_observer self.__new_record_cached = SimEvent(name="new_record_cached", sim=sim) self._random = Random() self.pending_events = [] # tuples with the form (when, action, record) self.records = [] # cached records
def __init__(self, **kwargs): """Constructor.""" self.halduplex = None self.__ifstate = CHANNELIF_RX # set up events and buffer self.txdata = SimEvent(name="txdata") self.txdone = SimEvent(name="txdone") self.rxdata = SimEvent(name="rxdata") self.rxdone = SimEvent(name="rxdone") self.rxbuffer = [] Element.__init__(self, **kwargs) # rename events self.txdata.name = "%s(%s).txdata"%(self.name, self.uid) self.txdone.name = "%s(%s).txdone"%(self.name, self.uid) self.rxdata.name = "%s(%s).rxdata"%(self.name, self.uid) self.rxdone.name = "%s(%s).rxdone"%(self.name, self.uid)
def __init__(self, usecsma=True, **kwargs): """Constructor.""" # create ARF parameters self.nsuccess = None self.nfailure = None self.timeout = None self.ackcount = {} self.arftimer = {} self.probation = {} self._rates = None # create ARF events self.dropack = SimEvent() self.recvack = SimEvent() DCF.__init__(self, usecsma=usecsma, **kwargs) # update event names self.dropack.name = "%s%s"%(self.name, ".dropack") self.recvack.name = "%s%s"%(self.name, ".recvack")
def __init__(self, duration, start=False, initstate=None, **kwargs): """Constructor. :param duration: Time for which `Timer` should run. :param start: Boolean; if true, `start()` immediately. :param initstate: Depricated (do not use). :param kwargs: Keywords passed to `FSM` constructor. """ assert (duration>0), "[TIMER]: Cannot simulate non-positive duration!" FSM.__init__(self, start=False, initstate=self.RUN, **kwargs) self.__tpassed = 0 self.__duration = duration self.__ctrlQ = Queue() self.__tic = None self.done = SimEvent(name=self.name+".done") self.kill = SimEvent(name=self.name+".kill") if start: self.start()
def block(self, thread, state): """Block a thread for this object.""" assert thread not in self.blockedThreads, \ '%s, %s, (%s)' %(self.ID, thread.ID, ','.join( [str(t.ID) for t in self.blockedThreads])) event = SimEvent() self.blockQueue.append(thread) self.blockedThreads[thread] = (state, event) return event
def __init__(self, subquery, sim, sender=None): super(ContinuousQuerier, self).__init__(sim=sim) self.sender = sender # if there is a unique response, it should stop querying # I will assume that there is not unique response (asking for PTR records) self.subquery = subquery self._random = Random() self.stopped = False self.__stop = SimEvent(name="stop_continuous_querier", sim=sim)
def run(self, aseed): """ PEM """ seed(aseed) self.dooropen = SimEvent("Door Open", sim=self) self.counter = Resource(1, name="Clerk", sim=self) doorman = Doorman(sim=self) self.activate(doorman, doorman.openthedoor()) source = Source(sim=self) self.activate(source, source.generate(number=5, rate=0.1), at=0.0) self.simulate(until=maxTime)
def __init__(self, *args, **kwargs): """Constructor.""" self._seqno, self._mode = 0, None self._payload = None # set parameters self.delay = None self.nsent, self.nrcvd = 0, 0 self.dest, self.plen = None, None self.recv, self.send = SimEvent(), SimEvent() self.recvbuffer = {} Element.__init__(self, *args, **kwargs)
def __init__(self, sim, name="SPP", tasks=list()): assert sim != None Process.__init__(self, name=name, sim=sim) self.tasks = tasks self.pending = list() # list of simtasks self.simtasks = list() self.arrival_event = SimEvent('Arrival Event', sim=sim)
def getWaitMsgEvents(self, tags): events = [] if not (isinstance(tags, list) or isinstance(tags, tuple)): tags = (tags, ) for tag in tags: if tag not in self.rtiNotifiers: event = SimEvent() self.rtiNotifiers[tag] = event event = self.rtiNotifiers[tag] events.append(event) return events
class NewWPDetector(Process): def __init__(self, mdns_instance, sim): super(NewWPDetector, self).__init__( sim = sim ) self.instance = mdns_instance self.last_wp_name = None self.__new_record_update = SimEvent(name="new_record_updated", sim=sim) def check_new_wps(self): while True: wp_r = self.instance.get_whitepage_record() if wp_r is not None: new_name = wp_r.node_name if self.last_wp_name != new_name: self.instance.notify_whitepage_changed() self.last_wp_name = new_name yield waitevent, self, (self.__new_record_update,) def notify_txt_record(self, record): if "iw" in record.keyvalues: self.__new_record_update.signal()
def __init__(self, cnode, ID, configs): IDable.__init__(self, '%s/sn%s' % (cnode.ID.split('/')[0], ID)) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = cnode.system self.configs = configs self.cnode = cnode self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024) self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread') self.groups = {} #{gid : group} self.newTxns = [] self.txnsRunning = set([]) self.shouldClose = False self.monitor = Profiler.getMonitor(self.ID) self.M_POOL_WAIT_PREFIX = '%s.pool.wait' % self.ID self.M_TXN_RUN_PREFIX = '%s.txn.run' % self.ID self.M_NUM_TXNS_RUN_KEY = '%s.num.txns' % self.ID self.runningThreads = set([]) self.closeEvent = SimEvent() self.newTxnEvent = SimEvent()
def __init__(self, interval, name=None, until=infinite, drift=('fixed', 0)): Process.__init__(self) self.interval = interval if name is not None: eventname = name else: eventname = "a_SimEvent" self.event = SimEvent(eventname) self.until = until try: key, mean, cfg = drift except ValueError: key, mean = drift cfg = {} lb = cfg.get('lb', 0); ub = cfg.get('ub', interval) if lb < 0: raise ValueError('drift lb = %s >= 0' %lb) if ub > interval: raise ValueError('drift ub = %s < %s = interval' %interval) cfg['lb'] = lb; cfg['ub'] = ub self.rgen = RandInterval.get(key, mean, cfg)
def __init__(self, cnode, ID, configs): IDable.__init__(self, '%s/sn%s'%(cnode.ID.split('/')[0], ID)) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = cnode.system self.configs = configs self.cnode = cnode self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024) self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread') self.groups = {} #{gid : group} self.newTxns = [] self.txnsRunning = set([]) self.shouldClose = False self.monitor = Profiler.getMonitor(self.ID) self.M_POOL_WAIT_PREFIX = '%s.pool.wait' %self.ID self.M_TXN_RUN_PREFIX = '%s.txn.run' %self.ID self.M_NUM_TXNS_RUN_KEY = '%s.num.txns'%self.ID self.runningThreads = set([]) self.closeEvent = SimEvent() self.newTxnEvent = SimEvent()
def __init__(self, actionNode, destinationNodes, url, data=None, waitUntil=10000.0, name="request", sim=None): super(RequestInstance, self).__init__(name=name, sim=sim) self.name += " (from=%s, url=%s)"%(actionNode.name, url) self.__actionNode = weakref.proxy(actionNode) #weakref.ref(actionNode) self.__destinationNodes = weakref.WeakSet(destinationNodes) # tuple with all the nodes to be requested self.url = url # accessible self.__data = data self.requestInit = {} # requestInit[reqId1] = now(), requestInit[reqId2] = now() self.responses = [] # accessible self.__maxWaitingTime = waitUntil self.nodeNamesByReqId = {} # used in the gossiping mechanism with the gossiping requests self.__newResponseReceived = SimEvent(name="request_response_for_%s"%(self.name), sim=sim) self.__observers = weakref.WeakSet()
def __init__(self, sim, name="SPNP", tasks=list()): assert sim != None Process.__init__(self, name=name, sim=sim) # list of pending activations self.pending = list() # list of blocker activations (usually one lower priority activation) self.blockers = list() # list of simtasks self.simtasks = list() # signals a new activation self.arrival_event = SimEvent('Arrival Event', sim=sim)
def __init__(self, system, ID, configs): IDable.__init__(self, 'zone%s/cn'%ID) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = system self.snodes = [] self.configs = configs self.groupLocations = {} self.txnsRunning = set([]) self.shouldClose = False self.closeEvent = SimEvent() #paxos entities self.paxosPRunner = None self.paxosAcceptor = None self.paxosLearner = None
class ClientNode(IDable, Thread, RTI): """Base client node. Base client node accepts txn requests and dispatch them to storage nodes. They are also hosts of paxos protocol entities. """ def __init__(self, system, ID, configs): IDable.__init__(self, 'zone%s/cn' % ID) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = system self.snodes = [] self.configs = configs self.groupLocations = {} self.txnsRunning = set([]) self.shouldClose = False self.closeEvent = SimEvent() #paxos entities self.paxosPRunner = None self.paxosAcceptor = None self.paxosLearner = None def addSNodes(self, snodes): self.snodes.extend(snodes) #notify new txn arrive, called by the system def onTxnArrive(self, txn): self.system.onTxnArrive(txn) self.txnsRunning.add(txn) self.dispatchTxn(txn) #notify new txn depart, called by the storage nodes def onTxnDepart(self, txn): if txn in self.txnsRunning: self.txnsRunning.remove(txn) self.system.onTxnDepart(txn) def dispatchTxn(self, txn): #just basic load balance hosts = self.getTxnHosts(txn) bestHost = iter(hosts).next() leastLoad = bestHost.load for host in hosts: if host.load < leastLoad: leastLoad = host.load bestHost = host bestHost.onTxnArrive(txn) self.logger.debug('%s dispatch %s to %s at %s' % (self.ID, txn.ID, bestHost, now())) return bestHost def getTxnHosts(self, txn): hosts = set([]) for gid in txn.gids: hosts.add(self.groupLocations[gid]) return hosts def close(self): self.logger.info('Closing %s at %s' % (self, now())) self.shouldClose = True self.closeEvent.signal() def _close(self): ##periodically check if we still have txn running #while True: # yield hold, self, 100 # if len(self.txnsRunning) == 0: # break for snode in self.groupLocations.values(): snode.close() for snode in self.groupLocations.values(): if not snode.isFinished(): yield waitevent, self, snode.finish try: self.paxosPRunner.close() self.paxosAcceptor.close() self.paxosLearner.close() except: pass def run(self): while not self.shouldClose: yield waitevent, self, self.closeEvent if self.shouldClose: for step in self._close(): yield step
class StorageNode(IDable, Thread, RTI): """Base storage node.""" def __init__(self, cnode, ID, configs): IDable.__init__(self, '%s/sn%s'%(cnode.ID.split('/')[0], ID)) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = cnode.system self.configs = configs self.cnode = cnode self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024) self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread') self.groups = {} #{gid : group} self.newTxns = [] self.txnsRunning = set([]) self.shouldClose = False self.monitor = Profiler.getMonitor(self.ID) self.M_POOL_WAIT_PREFIX = '%s.pool.wait' %self.ID self.M_TXN_RUN_PREFIX = '%s.txn.run' %self.ID self.M_NUM_TXNS_RUN_KEY = '%s.num.txns'%self.ID self.runningThreads = set([]) self.closeEvent = SimEvent() self.newTxnEvent = SimEvent() @property def load(self): return len(self.txnsRunning) + len(self.newTxns) def close(self): self.logger.info('Closing %s at %s'%(self, now())) self.shouldClose = True self.closeEvent.signal() def onTxnArrive(self, txn): self.newTxns.append(txn) self.newTxnEvent.signal() def onTxnsArrive(self, txns): self.newTxns.extend(txns) self.newTxnEvent.signal() def newTxnRunner(self, txn): class DefaultTxnRunner(Thread): def __init__(self, snode, txn): Thread.__init__(self) self.snode = snode self.txn = txn self.logger = logging.getLogger(self.__class__.__name__) def run(self): self.logger.debug('Running transaction %s at %s' %(txn.ID, now())) yield hold, self, RandInterval.get('expo', 100).next() return DefaultTxnRunner(self, txn) class TxnStarter(Thread): def __init__(self, snode, txn): Thread.__init__(self) self.snode = snode self.txn = txn def run(self): #add self and txn to snode self.snode.runningThreads.add(self) #wait for pool thread resource if necessary #self.snode.logger.debug( # '%s start txn=%s, running=%s, outstanding=%s' # %(self.snode, self.txn.ID, # '(%s)'%(','.join([t.ID for t in self.snode.txnsRunning])), # '(%s)'%(','.join([t.ID for t in self.snode.newTxns])) # )) self.snode.monitor.start( '%s.%s'%(self.snode.M_POOL_WAIT_PREFIX, self.txn.ID)) yield request, self, self.snode.pool self.snode.monitor.stop( '%s.%s'%(self.snode.M_POOL_WAIT_PREFIX, self.txn.ID)) #start runner and wait for it to finish thread = self.snode.newTxnRunner(self.txn) assert self.txn not in self.snode.txnsRunning, \ '%s already started txn %s'%(self.snode, self.txn) self.snode.txnsRunning.add(self.txn) self.snode.monitor.observe(self.snode.M_NUM_TXNS_RUN_KEY, len(self.snode.txnsRunning)) self.snode.monitor.start( '%s.%s'%(self.snode.M_TXN_RUN_PREFIX, self.txn.ID)) thread.start() yield waitevent, self, thread.finish self.snode.monitor.stop( '%s.%s'%(self.snode.M_TXN_RUN_PREFIX, self.txn.ID)) yield release, self, self.snode.pool #clean up self.snode.txnsRunning.remove(self.txn) self.snode.runningThreads.remove(self) self.snode.cnode.onTxnDepart(self.txn) def run(self): #the big while loop while True: yield waitevent, self, self.newTxnEvent while len(self.newTxns) > 0: #pop from new txn to running txn txn = self.newTxns.pop(0) #start thread = StorageNode.TxnStarter(self, txn) thread.start()
class ClientNode(IDable, Thread, RTI): """Base client node. Base client node accepts txn requests and dispatch them to storage nodes. They are also hosts of paxos protocol entities. """ def __init__(self, system, ID, configs): IDable.__init__(self, 'zone%s/cn'%ID) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = system self.snodes = [] self.configs = configs self.groupLocations = {} self.txnsRunning = set([]) self.shouldClose = False self.closeEvent = SimEvent() #paxos entities self.paxosPRunner = None self.paxosAcceptor = None self.paxosLearner = None def addSNodes(self, snodes): self.snodes.extend(snodes) #notify new txn arrive, called by the system def onTxnArrive(self, txn): self.system.onTxnArrive(txn) self.txnsRunning.add(txn) self.dispatchTxn(txn) #notify new txn depart, called by the storage nodes def onTxnDepart(self, txn): if txn in self.txnsRunning: self.txnsRunning.remove(txn) self.system.onTxnDepart(txn) def dispatchTxn(self, txn): #just basic load balance hosts = self.getTxnHosts(txn) bestHost = iter(hosts).next() leastLoad = bestHost.load for host in hosts: if host.load < leastLoad: leastLoad = host.load bestHost = host bestHost.onTxnArrive(txn) self.logger.debug('%s dispatch %s to %s at %s' %(self.ID, txn.ID, bestHost, now())) return bestHost def getTxnHosts(self, txn): hosts = set([]) for gid in txn.gids: hosts.add(self.groupLocations[gid]) return hosts def close(self): self.logger.info('Closing %s at %s'%(self, now())) self.shouldClose = True self.closeEvent.signal() def _close(self): ##periodically check if we still have txn running #while True: # yield hold, self, 100 # if len(self.txnsRunning) == 0: # break for snode in self.groupLocations.values(): snode.close() for snode in self.groupLocations.values(): if not snode.isFinished(): yield waitevent, self, snode.finish try: self.paxosPRunner.close() self.paxosAcceptor.close() self.paxosLearner.close() except: pass def run(self): while not self.shouldClose: yield waitevent, self, self.closeEvent if self.shouldClose: for step in self._close(): yield step
class Responder(Process): ANSWER_AT_FIELD = 0 QUERY_FIELD = 1 def __init__(self, sim, sender=None): super(Responder, self).__init__(sim=sim) self._random = Random() self.__new_query_queued = SimEvent(name="new_query_queued", sim=sim) self.local_records = {} # key: record, value: last time advertised self.queued_queries = [] # tuple: ( when to answer, query ) self.sender = sender def record_changes(self, record): for old_record in self.local_records.iterkeys(): if record == old_record: return record.have_data_changed(old_record) return False # if that record didn't exist before def write_record(self, record): if record in self.local_records: if self.record_changes(record): # "Whenever a host has a resource record with new data" # record is equal to a key (__eq__()==True), but has outdated data del self.local_records[record] # remove old key with outdated data # set new key self.local_records[record] = -1 # -1 => next time should be sent using multicast (because is an announcement) self.announce(record) else: # "Whenever a host has a resource record with new data" self.local_records[record] = -1 self.announce(record) def something_happened(self): # Whenever it might potentially be new data (e.g. after rebooting, waking from # sleep, connecting to a new network link, changing IP address, etc.) for record in self.local_records.iterkeys(): self.announce(record) # 10.2 Announcements to Flush Outdated Cache Entries def announce(self, announced_record): # TODO optimize to announce more than one? is that possible according to the standard? # Generating a fake query which will never be sent sq = SubQuery(announced_record.name, announced_record.type) # They may know my other records, I'm just announcing one known_answers = [record for record in self.local_records if record!=announced_record] q = Query( queries = [sq,], known_answers = known_answers ) # a little trick here, we queue a false query which will result in a response # containing the record we want to announce self.queue_query(q) def queue_query(self, query): # TODO optimization: # if the query is already planned, don't answer twice in such a short period of time # if an answer for the same query was answered in the last 1000 ms, wait for the response if query.response_is_unique(): # if the response is unique, answer within 10 ms when = self.sim.now() + self._random.random() * 10 self.queued_queries.append( (when, query) ) else: # delay between 20 and 120 ms when = self.sim.now() + 20 + self._random.random() * 100 self.queued_queries.append( (when, query) ) # sorts by the 1st element in the set self.queued_queries.sort(key=lambda tup: tup[0]) # wake up wait_for_next_event method self.__new_query_queued.signal() def answer(self): while True: if not self.queued_queries: # if it's empty... yield waitevent, self, (self.__new_query_queued,) else: next_query = self.queued_queries[0] if self.sim.now() < next_query[Responder.ANSWER_AT_FIELD]: twait = next_query[Responder.ANSWER_AT_FIELD] - self.sim.now() self.timer = Timer(waitUntil=twait, sim=self.sim) self.timer.event.name = "sleep_until_next_query" self.sim.activate(self.timer, self.timer.wait()) yield waitevent, self, (self.timer.event, self.__new_query_queued,) else: del self.queued_queries[0] # query will be processed self.process_query( next_query[Responder.QUERY_FIELD] ) def process_query(self, query): answers = self._get_possible_answers(query) self._suppress_known_answers(query, answers) if len(answers)>0: # avoid sending empty UDP messages! self._send_using_proper_method(query, answers) def _get_possible_answers(self, query): answers = [] for subquery in query.queries: for record in self.local_records.iterkeys(): if subquery.record_type == "PTR": # special queries in DNS-SD! if subquery.name == "_services._dns-sd._udp.local": answers.append( deepcopy(record) ) # all of the records elif record.name.endswith(subquery.name): answers.append( deepcopy(record) ) # all of the records elif subquery.name == record.name and subquery.record_type == record.type: answers.append( deepcopy(record) ) return answers def _suppress_known_answers(self, query, answers): # Section 7.1. Known-Answer Suppression for known_answer in query.known_answers: for record in answers: if known_answer.name == record.name and known_answer.type == record.type: answers.remove(record) def _send_using_proper_method(self, query, answers): unicast = query.question_type is "QU" # unicast type # See 5.4 Questions Requesting Unicast Responses if unicast: # event if it was marked as unicast, can be sent as multicast for record in answers: thresold_time = record.ttl * 1000 * 0.25 # ttl is measured in seconds and simulation time in ms! last_time_sent = self.local_records[record] now = self.sim.now() if last_time_sent==-1: # never sent before unicast = False self.local_records[record] = now elif ( now - last_time_sent ) > thresold_time: unicast = False # not recently advertised, send using multicast self.local_records[record] = now if unicast: self.sender.send_unicast( query.to_node, DNSPacket(ttype=DNSPacket.TYPE_RESPONSE, data=answers) ) else: self.sender.send_multicast( DNSPacket(ttype=DNSPacket.TYPE_RESPONSE, data=answers) )
class RequestInstance(Process): # TODO rename to something more meaningful such as RequestSender """ This class performs an HTTP request in SimPy """ ReqIdGenerator = 0 def __init__(self, actionNode, destinationNodes, url, data=None, waitUntil=10000.0, name="request", sim=None): super(RequestInstance, self).__init__(name=name, sim=sim) self.name += " (from=%s, url=%s)"%(actionNode.name, url) self.__actionNode = weakref.proxy(actionNode) #weakref.ref(actionNode) self.__destinationNodes = weakref.WeakSet(destinationNodes) # tuple with all the nodes to be requested self.url = url # accessible self.__data = data self.requestInit = {} # requestInit[reqId1] = now(), requestInit[reqId2] = now() self.responses = [] # accessible self.__maxWaitingTime = waitUntil self.nodeNamesByReqId = {} # used in the gossiping mechanism with the gossiping requests self.__newResponseReceived = SimEvent(name="request_response_for_%s"%(self.name), sim=sim) self.__observers = weakref.WeakSet() def startup(self): t_init = self.sim.now() for node in self.__destinationNodes: # already removed from the list prior to calling to this method, but just in case... if node is not self.__actionNode: reqId = RequestInstance.ReqIdGenerator RequestInstance.ReqIdGenerator += 1 request = HttpRequest(reqId, self.url, data=self.__data) self.nodeNamesByReqId[reqId] = node.name self.requestInit[reqId] = self.sim.now() node.queueRequest(self, request) #if self.__data!=None: # G.executionData.requests['data-exchanged'] += len(self.__data) else: raise Exception("A request to the same node is impossible! ") self.timer = Timer(waitUntil=G.timeout_after, sim=self.sim) self.timer.event.name = "request_timeout_for_%s"%(self.name) self.sim.activate(self.timer, self.timer.wait())#, self.__maxWaitingTime) while not self.allReceived() and not self.timer.ended: yield waitevent, self, (self.timer.event, self.__newResponseReceived,) if not self.allReceived(): # timeout reached #print "Response not received!" response_time = self.sim.now() - t_init for node_name in self.get_unanswered_nodes(): G.traceRequest(t_init, self.__actionNode.name, node_name, self.url, 408, # TIMEOUT. See http://www.restlet.org/documentation/2.0/jse/api/org/restlet/data/Status.html#CLIENT_ERROR_REQUEST_TIMEOUT response_time ) # self.__actionNode.addClientRequestActivityObservation(now()-init, now()) # this information can be extracted from the traces # G.executionData.requests['failure'].append(self) for o in self.__observers: o.notifyRequestFinished(self) def get_unanswered_nodes(self): # not yet deleted requestInit keys, are the ids without a response return [self.get_destination_node_name(reqId) for reqId in self.requestInit.keys()] def getWaitingFor(self): return len(self.requestInit) def allReceived(self): return self.getWaitingFor()==0 def addResponse(self, response): # TODO associate with a node #if response.getstatus()==404: # dest_node_name = self.get_destination_node_name(response.getid()) # print dest_node_name # timeouts have been already taken into account in the 'timeout' counter if not self.timer.ended: t_init = self.requestInit[response.getid()] response_time = self.sim.now() - t_init G.traceRequest(t_init, self.__actionNode.name, self.get_destination_node_name(response.getid()), response.geturl(), response.getstatus(), response_time ) #G.executionData.response_time_monitor.observe( now() - t_init ) # request time del self.requestInit[response.getid()] self.responses.append(response) #dest_node_name #G.executionData.requests['data-exchanged'] += len(response.get_data()) #fileHandle = open ( 'test.txt', 'a' ) #fileHandle.write ( response.get_data() ) #fileHandle.close() self.__newResponseReceived.signal() def get_destination_node_name(self, responseId): return self.nodeNamesByReqId[responseId] def toString(self): for resp in self.responses: print resp.getmsg() def getActionNode(self): return self.__actionNode def addObserver(self, observer): self.__observers.add(observer)
class Cache(Process): WHEN_FIELD = 0 ACTION_FIELD = 1 # what to do RECORD_FIELD = 2 EVENT_KNOWN_ANSWER = "add_to_known_answer_suppression" EVENT_RENEW = "try_to_renew" EVENT_FLUSH= "flush_record" def __init__(self, sim, record_observer=None): super(Cache, self).__init__(sim=sim) self.record_observer = record_observer self.__new_record_cached = SimEvent(name="new_record_cached", sim=sim) self._random = Random() self.pending_events = [] # tuples with the form (when, action, record) self.records = [] # cached records def get_known_answers(self): known_answers = [] for record in self.records: found = False for event in self.pending_events: if event[Cache.ACTION_FIELD] == Cache.EVENT_KNOWN_ANSWER and event[Cache.RECORD_FIELD] == record: found = True break if found: known_answers.append( deepcopy(record) ) return known_answers def _delete_events_for_record(self, record): to_delete = [] for event in self.pending_events: if event[Cache.RECORD_FIELD] == record: to_delete.append(event) for event in to_delete: self.pending_events.remove(event) def _get_time_after_percentage(self, ttl, percentage): """Percentage example: 0.45 (means 45%)""" # remember that ttl is measured in seconds and simulation time in ms! return self.sim.now() + ttl * 1000 * percentage def _create_new_events(self, record): ttl = record.ttl # at 1/2 of the TTL => does not add to known answer suppression when = self._get_time_after_percentage(ttl, 0.5) self.pending_events.append( (when, Cache.EVENT_KNOWN_ANSWER, record) ) # section 5.2, http://tools.ietf.org/html/draft-cheshire-dnsext-multicastdns-15 # at 80%, 85%, 90% and 95% of the TTL => try to renew the record for percentage in ( 80, 85, 90, 95 ): percentage_with_variation = (percentage + self._random.random()*2) / 100.0 # 2% of variation when = self._get_time_after_percentage(ttl, percentage_with_variation) self.pending_events.append( (when, Cache.EVENT_RENEW, record) ) def cache_record(self, record): self._delete_events_for_record(record) self._create_new_events(record) if record in self.records: self.records.remove(record) # does delete the previous one? self.records.append(record) # sorts by the 1st element in the set self.pending_events.sort(key=lambda tup: tup[0]) # wake up wait_for_next_event method self.__new_record_cached.signal() def flush_all(self): del self.records[:] del self.pending_events[:] # Inspired by RequestInstance class def wait_for_next_event(self): while True: if not self.pending_events: # if it's empty... yield waitevent, self, (self.__new_record_cached,) else: next_event = self.pending_events[0] if self.sim.now() < next_event[Cache.WHEN_FIELD]: twait = next_event[Cache.WHEN_FIELD]-self.sim.now() self.timer = Timer(waitUntil=twait, sim=self.sim) self.timer.event.name = "sleep_until_next_event" self.sim.activate(self.timer, self.timer.wait()) yield waitevent, self, (self.timer.event, self.__new_record_cached,) else: del self.pending_events[0] # action will be taken if next_event[Cache.ACTION_FIELD] == Cache.EVENT_FLUSH: # delete old record self.records.remove( next_event[Cache.RECORD_FIELD] ) elif next_event[Cache.ACTION_FIELD] == Cache.EVENT_RENEW: if self.record_observer is not None: self.record_observer.renew_record( next_event[Cache.RECORD_FIELD] )
class DSR(Routing): """Dynamic Source Routing protocol (RFC 4728). This implementation uses link-level acknowledgements to do route maintenance (see `MAC.drpdata` and `MAC.ackdata`). :ivar rreqtable: `RouteRequestTable` used to manage route requests. :ivar rreqrate: Rate annotation applied to all Route Requests. :ivar datarate: Rate annotation applied to all unicast messages. :ivar maintbuffer: Dictionary containing buffer of packets being maintained by DSR, indexed by next hop addresses. :cvar mac: Property to access pointer to `MAC`. :cvar rrt: Alias for `rreqtable`. :cvar MaxMaintRexmt: Maximum number of retransmission for route maintenance. """ name = "DSR" tracename = "DSR" MaxMaintRexmt = 0 DiscoveryHopLimit = 255 MaxTTL = DiscoveryHopLimit BroadcastJitter = 10e-3 MaintenanceTimeout = 10.0 RexmtBufferSize = 50 MaxGratuitousRexmt = 6 def __init__(self, *args, **kwargs): """Constructor.""" # set up parameters self.__mac = None self.rreqrate = None self.datarate = None self.maintbuffer = None # additional parameters for signalling self.sndrreq = SimEvent() self.drprreq = SimEvent() self.finrreq = SimEvent() self.sndfail = SimEvent() # call base constructor Routing.__init__(self, *args, **kwargs) mac = property(fget=lambda self: self.get_mac(), \ fset=lambda self, m: self.set_mac(m) ) rrt = property(fget=lambda self: self.rreqtable) promiscuous = property(fget=lambda self: self.get_promiscuous() ) def configure(self, mac=None, rreqrate=None, datarate=None, **kwargs): """Configure pointers and parameters. :param mac: `MAC` module corresponding to this network protocol. :param rreqrate: Rate index used for flooding RREQ messages. :param datarate: Rate index used for sending other DSR messages. :param kwargs: Additional keywords passed to `Routing.configure()`. If `mac` is not provided, this module will attempt to automatically determine the appropriate `MAC`. If `rreqrate` and/or `datarate` are not specified, `DSR` will not attempt to set the rate annotation of the outgoing packet. It is important that the `rreqrate` be higher than `datarate` to ensure stable links for DSR. """ Routing.configure(self, **kwargs) self.mac = mac self.rreqrate = rreqrate self.datarate = datarate self.maintbuffer = {} self.table = self.newchild('routingtable', RouteCache, \ name=self.name+".rcache", \ tracename=self.tracename+".RCACHE") rreqtable = self.newchild('rreqtable', RouteRequestTable, \ name=self.name+".rreqtable", \ tracename=self.tracename+".RREQTBL") ############################## # TX STATES ############################## def IPFORWARD(self, fsm, p): """IPFORWARD state; start route maintenance for IP+DSR if needed. :param p: IP packet to send. :note: This state assumes a valid IP+DSR packet was went to it. """ # get IP parameters ip, dsr = p[IP], p[DSRPacket] src, dst = ip.src, ip.dst # start route maintenance or deliver self.checkdsropt(ip, exception=True, incoming=False) # send broadcast immediately if (dst==self.broadcast): yield fsm.goto(self.IPDELIVER, ip, dst) # otherwise ... srcroute = getdsr_srcroute(ip) if srcroute: path = srcroute.addresses segsleft, naddr = srcroute.segsleft, len(path) nexthop = path[-segsleft] else: nexthop = dst # cache route to nexthop and start route maintenance for ip self.addroute(nexthop) self.maintain(ip, nexthop) # put in maintbuffer def IPROUTING(self, fsm, p): """IPROUTING state; start route discovery for `p`.""" # error messages dropbuffer = "sendbuffer overflow" # get IP/DSR/Option parameters ip, dsr = p[IP], p[DSRPacket] src, dst = ip.src, ip.dst # has route -> resend if self.hasroute(dst): yield fsm.goto(self.IPSEND, ip, src, dst) # otherwise -> start route discovery rre = self.rrt.getentry(dst) drop = rre.buffer(p) kwargs = {'src':src, 'dst':dst} kwargs['nbuffered'] = len(rre.sendbuffer) kwargs['timeleft'] = time2msec(rre.timeleft()) self.debug("SENDBUFF", p, **kwargs) for d in drop: self.log_drop(d, drop=dropbuffer) # send RREQ yield fsm.goto(self.TXRREQ, dst) ############################## # TX DSR OPTIONS ############################## def TXRREQ(self, fsm, target, options=[], rexmt=0): """TXRREQ state; create and send route request. :param target: Target for route discovery. :param options: Additional DSR options [default=None]. :note: This state is persistent. It will keep trying to send until """ # error messages rreqerror = "[DSR]: Error getting RREQ Table entry!" droprexmt = "max rexmt exceeded" drophasrt = "route already exists" # pause for jitter jitter = random.uniform(0,1)*self.BroadcastJitter yield hold, fsm, jitter # check route and rexmt count if self.hasroute(target): self.log("RREQSTOP", target=target, rexmt=rexmt, drop=drophasrt) rre = self.rrt.getentry(target) while rre.sendbuffer: p = rre.sendbuffer.pop(0) f = FSM.launch(self.IPRECOVER, p, self.address, target) self.rrt.delentry(target) # signal that RREQ has finished self.finrreq.signal(target) yield fsm.stop() # HALT and stop sending RREQ if (rexmt>self.rrt.MaxRequestRexmt): self.log("RREQDROP", target=target, rexmt=rexmt, drop=droprexmt) rre = self.rrt.getentry(target) while rre.sendbuffer: p = rre.sendbuffer.pop(0) self.log_drop(p, drop=droprexmt) self.rrt.delentry(target) # signal that RREQ has been abandoned self.drprreq.signal(target) yield fsm.stop() # HALT and stop sending RREQ # get RREQ parameters sendrreq = self.rrt.sendrreq(target) rre = self.rrt.getentry(target) tleft = rre.timeleft() # get parameters for logging kwargs = {'rexmt':rexmt, 'nbuffered': len(rre.sendbuffer)} kwargs['options'] = [o.tracename for o in options] kwargs['jitter'] = time2msec(jitter) kwargs['timeleft'] = time2msec(tleft) # cannot send RREQ? -> RREQ is busy, drop attempt if not sendrreq: self.debug("RREQBUSY", target=target, **kwargs) yield fsm.stop() # HALT and allow other RREQ to finish # otherwise -> send RREQ ID, ttl = rre.ID, rre.ttl # create DSR+RREQ+options nextheader = self.getproto(None) dsr = DSRPacket(nextheader=nextheader) rreq = DSROPT_RREQ(identification=ID, target=target) dsr.options = [rreq] + [o for o in options] # create IP+DSR proto = self.getproto(dsr) src, dst = self.address, self.broadcast ip = IP(src=src, dst=dst, proto=proto, ttl=ttl) ip.add_payload(dsr) # send RREQ -> wait for timeout, then rexmt self.debug("TXRREQ", ip, target=target, **kwargs) f = FSM.launch(self.IPDELIVER, ip, dst) # signal that RREQ has been sent to target self.sndrreq.signal(target) # wait for send RREQ to timeout before trying again yield hold, fsm, tleft yield fsm.goto(self.TXRREQ, target, options, rexmt+1) def TXRREP(self, fsm, src, dst, rreq, addresses): """TXRREP state; create and send route reply to `dst`.""" self.debug("TXRREP", src=src, dst=dst, addresses=addresses) # cache reverse route from rreq? opt = getdsr_rreq(rreq) if opt and DSR_USE_REVERSE_ROUTES: rpath = [a for a in opt.addresses] rpath.reverse() if rpath: self.addroute(dst, rpath) # more than one hop away else: self.addroute(dst) # one hop neighbor # create RREP option rrep = DSROPT_RREP() rrep.addresses = [a for a in addresses] # no route to dst -> send RREQ+RREP if not self.hasroute(dst): yield fsm.goto(self.TXRREQ, dst, options=[rrep]) # otherwise -> ... if opt and DSR_USE_REVERSE_ROUTES: path = [a for a in rpath] else: path = self.srcroute(dst) ttl = len(path)+1 # create DSR nextheader = self.getproto(None) dsr = DSRPacket(nextheader=nextheader) dsr.options = [rrep] # create IP proto = self.getproto(dsr) ip = IP(src=src, dst=dst, proto=proto, ttl=ttl) ip.add_payload(dsr) # add SRCROUTE? pkt = self.updateroute(ip, path) #assert (pkt is not None) assert self.safe((pkt is not None)) yield fsm.goto(self.IPFORWARD, pkt) def TXRERR(self, fsm, src, dst, errortype, type_specific): """TXRERR state; create and send a new route error message.""" self.debug("TXRERR", src=src, dst=dst, unreachable=type_specific) # erro messages errornotme = "[DSR]: Cannot send RERR not from me!" assert self.safe(src==self.address), errornotme # create RERR option rerr = DSROPT_RERR() rerr.err_src, rerr.err_dst = src, dst rerr.errortype, rerr.type_specific = errortype, type_specific # create DSR + RERR Option nextheader = self.getproto(None) dsr = DSRPacket(nextheader=nextheader) dsr.options = [rerr] # create IP + DSR proto = self.getproto(dsr) ip = IP(src=src, dst=dst, proto=proto) ip.add_payload(dsr) # no route -> start route discovery? if not self.hasroute(dst): yield fsm.goto(self.IPSEND, ip, src, dst) # otherwise -> ... path = self.srcroute(dst) ttl = len(path)+1 # add SRCROUTE? ip.ttl = ttl pkt = self.updateroute(ip, path) #assert (pkt is not None) assert self.safe((pkt is not None)) yield fsm.goto(self.IPFORWARD, pkt) def MAINT(self, fsm, nexthop, rexmt=0, send=True): """MAINT state; perform route maintenace for nexthop. :note: Assume maintenance buffer contains valid IP+DSR packets. """ yield hold, fsm, 0 # yield to other threads #assert (nexthop in self.maintbuffer) assert self.safe((nexthop in self.maintbuffer)) # error messages droproute = "broken route" # get maintenance parameters buff = self.maintbuffer[nexthop]['buffer'] if (len(buff)<1): del self.maintbuffer[nexthop] yield fsm.stop() # HALT and stop maintenance # get head of buffer p = buff[0] ip, dsr = p[IP], p[DSRPacket] addr, src, dst = self.address, ip.src, ip.dst # check if path is still valid opt, path = getdsr_srcroute(ip), None # one hop away? if opt: segsleft = opt.segsleft # more than one hop? path = [a for a in opt.addresses[-segsleft:]] pathok = self.hasroute(dst, path) # if path is broken -> drop or resend if not pathok: p = buff.pop(0) if (addr==src): f = FSM.launch(self.IPRECOVER, p, src, dst) else: self.log_drop(p, drop=droproute) yield fsm.goto(self.MAINT, nexthop) # continue with next packet # otherwise -> send head of buffer? if send: f = FSM.launch(self.IPDELIVER, ip, nexthop) # wait for link-level feedback (ACK/DROP) mac, feedback = self.mac, None yield waitevent, fsm, (mac.ackdata, mac.drpdata) if (mac.ackdata in fsm.eventsFired): p = mac.ackdata.signalparam if self.issame(ip, p): feedback = "ackdata" elif (mac.drpdata in fsm.eventsFired): p = mac.drpdata.signalparam if self.issame(ip, p): feedback = "drpdata" # process feedback if (feedback=="ackdata"): p = buff.pop(0) yield fsm.goto(self.MAINT, nexthop) # continue with next packet elif (feedback=="drpdata"): rexmt += 1 norexmt = (rexmt>self.MaxMaintRexmt) # rexmt not exceeded -> try again if not norexmt: self.debug("REXMT%d"%(rexmt), ip) yield fsm.goto(self.MAINT, nexthop, rexmt) # otherwise -> broken link!! etype = DSR_ERROR_NODE_UNREACHABLE esrc, unreachable = self.address, nexthop self.debug("DROPDATA", src=esrc, unreachable=unreachable) self.removelink(esrc, unreachable) # signal broken link self.sndfail.signal(esrc) # clear out maintenance buffer errdst = [] while buff: p = buff.pop(0) ip, dsr = p[IP], p[DSRPacket] src, dst = ip.src, ip.dst # send RERR (for non-RREP messages) rrep = getdsr_rrep(ip) sendrerr = (not rrep) and (src not in errdst) # recover packet or send RERR if (addr==src): f = FSM.launch(self.IPRECOVER, p, src, dst) elif sendrerr: errdst.append(src) # send RERR to sources for edst in errdst: f = FSM.launch(self.TXRERR, esrc, edst, etype, unreachable) # continue to allow graceful shutdown yield fsm.goto(self.MAINT, nexthop) else: # feedback not for me -> continue waiting yield fsm.goto(self.MAINT, nexthop, rexmt, send=False) def IPRECOVER(self, fsm, p, src, dst): """IPRECOVER state; attempt to resend packet `p`. :note: This method assumes that `p` is a valid IP+DSR packet. """ self.debug("IPRECOVER", p, src=src, dst=dst) # error messages errornotme = "[DSR]: Cannot recover message not from me!" errorbcast = "[DSR]: Cannot recover broadcast messages!" droprcount = "recover attempts exceeded" assert self.safe(src==self.address), errornotme assert self.safe(dst!=self.broadcast), errorbcast # drop RREP or RERR packets rrep = getdsr_rrep(p) rerr = getdsr_rerr(p) drop = "" if rrep: drop = "do not recover RREP" if rerr: drop = "do not recover RERR" if drop: self.log_drop(p, src=src, dst=dst, drop=drop) yield fsm.stop() # HALT and discard packet # keep secret annotation for keeping track of recovery attempts rcount = 0 if p.hasanno('iprecover'): rcount = p.getanno('iprecover') + 1 p.setanno('iprecover', rcount, priv=True) # max retries exceeded? if (rcount>1): # FIXME: use (>self.MaxMaintRexmt) instead? self.log_drop(p, src=src, dst=dst, rcount=rcount, drop=droprcount) yield fsm.stop() # otherwise -> remove stale route, update, and resend ip, dsr = p[IP], p[DSRPacket] dsr.options = [o for o in dsr.options if (not getdsr_srcroute(o))] assert (dsr.nextheader!=const.IP_PROTO_NONE) yield fsm.goto(self.IPSEND, ip, src, dst) ############################## # RX STATES ############################## def IPCLASSIFY(self, fsm, p, cache=True): """IPCLASSIFY state; overloaded to implement DSR classification. :param p: Received IP packet. :param cache: Cache routing information from `p`. :note: This state assumes that packet `p` passed all validity checks in `IPRECV` (i.e. `checkiprecv()`). """ errmsg = "[DSR]: IPCLASSIFY does not support promiscuous mode." #assert (not self.promiscuous), errmsg assert self.safe((not self.promiscuous)), errmsg # error messages errordsropt = "[DSR]: Unprocessed DSR options still remain!" dropnotforme = "not for me" # get IP+DSR packets ip = p[IP] dsr = ip[DSRPacket] # get IP/DSR parameters addr, bcast = self.address, self.broadcast src, dst = ip.src, ip.dst # get DSR options rreq, rrep = getdsr_rreq(ip), getdsr_rrep(ip) rerr, srcroute = getdsr_rerr(ip), getdsr_srcroute(ip) isforme, isbcast = (dst==addr), (dst==bcast) unicast, promiscuous = (not isbcast), self.promiscuous # cache routing info from packet if cache: self.cacheroute(ip) # classify DSR options if srcroute and unicast: yield fsm.goto(self.RXSRCROUTE, ip, dsr, srcroute) elif rreq and isbcast: yield fsm.goto(self.RXRREQ, ip, dsr, rreq) elif rrep and isforme: yield fsm.goto(self.RXRREP, ip, dsr, rrep) elif rerr and isforme: yield fsm.goto(self.RXRERR, ip, dsr, rerr) elif dsr.options: #assert (self.promiscuous or (not isforme)), errordsropt assert self.safe((self.promiscuous or (not isforme))), errordsropt # classify payload of packet if (dst==addr) or (dst==bcast): # packet for me -> detach payload payload = dsr.payload dsr.remove_payload() self.log_recv(payload, src=src, dst=dst) if (dsr.nextheader==const.IP_PROTO_IP): yield fsm.goto(self.IPRECV, payload) # IPENCAP -> reclassify elif (dsr.nextheader!=const.IP_PROTO_NONE): pkt = self.set_recvanno(payload, src, dst) yield self.TXU.send(fsm, [pkt]) # DATA -> send to upper layer else: # otherwise -> drop self.log_drop(ip, drop=dropnotforme) ############################## # RX DSR OPTIONS ############################## def RXSRCROUTE(self, fsm, ip, dsr, opt): """RXSRCROUTE state; process source route option. :note: Assumes `checkiprecv()` passed. """ self.debug("RXSRCROUTE", ip) # get IP/DSR/Option parameters segsleft = opt.segsleft path = opt.addresses[-segsleft] # forward packet to next hop in source route if (segsleft>1): # intermediate hop -> update SRCROUTE and send ip.ttl = ip.ttl - 1 opt.segsleft = segsleft - 1 yield fsm.goto(self.IPFORWARD, ip) elif (segsleft==1): # last hop -> strip SRCROUTE and send ip.ttl = ip.ttl - 1 dsr.options = [o for o in dsr.options if (not getdsr_srcroute(o))] yield fsm.goto(self.IPFORWARD, ip) def RXRREQ(self, fsm, ip, dsr, opt): """RXRREQ state; process route request option. :note: Assumes `checkiprecv()` passed. """ self.debug("RXRREQ", ip) # error messages droprreq = "in source route or not new RREQ" # get IP/DSR/Option parameters addr, src, dst = self.address, ip.src, ip.dst ID, target = opt.identification, opt.target rreqonly = (dsr.nextheader==const.IP_PROTO_NONE) # check if target reached if (addr==target): # send RREP [and process remaining packet] path = [a for a in opt.addresses] + [target] f = FSM.launch(self.TXRREP, addr, src, opt, path) # strip RREQ and set dst=target dsr.options = [o for o in dsr.options if (not getdsr_rreq(o))] ip.dst = target # check for other options rerr, rrep = getdsr_rerr(dsr), getdsr_rrep(dsr) if rerr: yield fsm.goto(self.RXRERR, ip, dsr, rerr) elif rrep: yield fsm.goto(self.RXRREP, ip, dsr, rrep) # RREQ only -> HALT and discard packet if rreqonly: yield fsm.stop() # otherwise -> remove DSR options and reclassify dsr.options = [] yield fsm.goto(self.IRECV, ip) # otherwise -> attempt to forward RREQ assert (addr!=target) inroute = (src==addr) or (addr in opt.addresses) newrreq = self.rrt.addcache(src, ID, target) # check if RREQ should be dropped if (inroute or (not newrreq)): self.log_drop(ip, drop=droprreq) yield fsm.stop() # HALT and discard packet # update options and forward RREQ ip.ttl = ip.ttl - 1 opt.addresses = [a for a in opt.addresses] + [addr] yield fsm.goto(self.IPFORWARD, ip) def RXRREP(self, fsm, ip, dsr, opt): """RXRREP state; handle route reply message. :note: Assumes `checkiprecv()` passed. """ self.debug("RXRREP", ip) # get IP/DSR/Option parameters src, dst = ip.src, ip.dst addr, target = self.address, opt.addresses[-1] rreponly = (dsr.nextheader==const.IP_PROTO_NONE) # only keep RREP options and update route cache assert (dst==addr) dsr.options = [o for o in dsr.options if (getdsr_rrep(o))] self.cacheroute(ip) #assert (self.hasroute(target)) assert self.safe((self.hasroute(target))) # verify route to target # get entry from Route Request Table and forward packets rre = self.rrt.getentry(target) while rre.sendbuffer: p = rre.sendbuffer.pop(0) f = FSM.launch(self.IPRECOVER, p, addr, target) self.rrt.delentry(target) # more than RREP? if not rreponly: # remove DSR options and reclassify dsr.options = [] yield fsm.goto(self.IRECV, ip) def RXRERR(self, fsm, ip, dsr, opt): """RXRERR state; handle route error message. :note: Assumes `checkiprecv()` passed. """ self.debug("RXRRER", ip) # error messages droptype = "unsupported route error type" # get IP/DSR/Option parameters addr, src, dst = self.address, ip.src, ip.dst errsrc, errdst = opt.err_src, opt.err_dst errortype, unreachable = opt.errortype, opt.type_specific rerronly = (dsr.nextheader==const.IP_PROTO_NONE) #assert (addr==dst) assert self.safe((addr==dst)) # process RERR packet (or drop unsupported errortype) if (errortype==DSR_ERROR_NODE_UNREACHABLE): self.removelink(errsrc, unreachable) else: self.log_drop(ip, drop=droptype) # more than RERR? if not rerronly: # remove DSR options and reclassify dsr.options = [] yield fsm.goto(self.IRECV, ip) ############################## # HELPER METHODS ############################## def encap_data(self, p, src, dst, force=False, **kwargs): """Encapsulate data packet (if needed) for delivery to next hop. :param p: Packet to deliver. :param src: Source address. :param dst: Destination address. :param force: If true, force encapsulation into a new IP packet. :param kwargs: Additional keywords passed to IP constructor. :return: Encapsulated/modified packet for delivery. If `p` is already an IP packet and `force` is false, this method will update 'src', 'dst', and 'ttl' fields as needed. Otherwise, this method encapsulates `p` with an IP header. :note: By default this method assumes that `ptype` indicates IPv4. *Overload this method to handle other protocol types*. """ # if no encapsulation needed -> update parameters isip = isinstance(p, Packet) and p.haslayer(IP) isdsr = isip and p.haslayer(DSRPacket) if isdsr and (not force): ip, dsr = p[IP], p[DSRPacket] dsr.nextheader = self.getproto(dsr.payload) if self.hasroute(ip.dst): p = self.updateroute(ip) # update IP parameters return Routing.encap_data(self, p, src, dst, force=False, **kwargs) # create DSR nextheader = self.getproto(p) dsr = DSRPacket(nextheader=nextheader) dsr.add_payload(p) # create IP+DSR proto = self.getproto(dsr) ip = IP(src=src, dst=dst, proto=proto, **kwargs) ip.add_payload(dsr) # update and return return self.encap_data(ip, src, dst, force=False, **kwargs) def maintain(self, p, nexthop): """Start or udpate route maintenace on IP+DSR packet. :note: This assumes a IP+DSR packet was passed to it. """ # error messages errornexthop = "[DSR]: maintain() found invalid nexthop!" dropbuffer = "maintenance buffer overflow" dropttl = "TTL expired" # get IP/DSR/Option parameters ip, dsr = p[IP], p[DSRPacket] addr, src, dst, ttl = self.address, ip.src, ip.dst, ip.ttl # TTL expired? if (ttl<1): self.log_drop(p, drop=dropttl) return # start or continue route maintenance if nexthop in self.maintbuffer: # add packet to end of maintenance buffer buff = self.maintbuffer[nexthop]['buffer'] if (len(buff)<self.RexmtBufferSize): buff.append(ip) self.debug("MAINTBUFF", ip) else: self.log_drop(ip, drop=dropbuffer) else: # add new buffer and launch thread to maintain it self.maintbuffer[nexthop] = {'buffer':[ip]} f = FSM.launch(self.MAINT, nexthop) def cacheroute(self, p): """Cache relevant route information from IP+DSR packet. :note: This method assumes that the packet has passed through `checkiprecv()`. """ errmsg = "[DSR]: cacheroute() does not support promiscuous mode." #assert (not self.promiscuous), errmsg assert self.safe((not self.promiscuous)), errmsg # get ip/dsr packets and parameters ip = p[IP] dsr = p[DSRPacket] addr, bcast = self.address, self.broadcast src, dst, ttl = ip.src, ip.dst, ip.ttl # classify based on DSR option rreq, rrep = getdsr_rreq(ip), getdsr_rrep(ip) rerr, srcroute = getdsr_rerr(ip), getdsr_srcroute(ip) # process source route option if srcroute: # cache remaining route to dst segsleft = srcroute.segsleft path = srcroute.addresses[-segsleft:] if (segsleft>1): self.addroute(dst, path[1:]) elif (segsleft==1): self.addroute(dst) # process route request option if rreq: # no routing info to cache, but use target as dst for other options dst = rreq.target # process route reply option if rrep: # check if on route or intended dst of RREP inroute = (addr in rrep.addresses[:-1]) isforme = (dst==addr) # cache routing info idx = None if inroute: idx = rrep.addresses.index(addr)+1 elif isforme: idx = 0 if (idx is not None): target = rrep.addresses[-1] rt = rrep.addresses[idx:-1] if rt: self.addroute(target, rt) # more than one hop away else: self.addroute(target) # target is neighbor # process route error option if rerr: errsrc, unreachable = rerr.err_src, rerr.type_specific if (rerr.errortype==DSR_ERROR_NODE_UNREACHABLE): self.removelink(errsrc, unreachable) def checkiprecv(self, p, nextheader=True, **kwargs): """Overloaded to check for valid IP+DSR packet. :param p: Packet received in `IPRECV`. :param args: Additional arguments passed to base class method. :param kwargs: Additional keywords passed to base class method. :return: String containing any error condition, or nothing for no error. """ # error messages dropnondsr = "non-IP+DSR packet in IPRECV" dropheader = "invalid IP+DSR nextheader" dropdsropt = "invalid DSR options!" # call base method drop = Routing.checkiprecv(self, p, **kwargs) if drop: return drop # valid IP+DSR? isdsr = p.haslayer(IP) and p.haslayer(DSRPacket) if not isdsr: return dropnondsr dsr = p[DSRPacket] # check nextheader if nextheader: payproto = self.getproto(dsr.payload) if (payproto!=dsr.nextheader): return dropheader # check DSR options if not self.checkdsropt(p, exception=False): return dropdsropt return "" # no error def checkdsropt(self, p, exception=True, incoming=True): """Check DSR options of incoming packet for any violations. :param p: Packet to check. :param exception: If true, throws exception for violation. :param incoming: If true, treat `p` as a received packet. :return: Boolean flag; if True, DSR packet is okay. :note: Do not use this method to check options for outgoing packets without setting `incoming` to False. """ if isinstance(p, Reference): p = p._deref isip = isinstance(p, Packet) and p.haslayer(IP) isdsr = isip and p.haslayer(DSRPacket) if not isdsr: if exception: raise RuntimeError, "[DSR]: non-IP+DSR packet!" return False # get IP+DSR packets ip = p[IP] dsr = ip[DSRPacket] # get IP/DSR parameters addr, bcast = self.address, self.broadcast src, dst = ip.src, ip.dst # classify DSR options rreq, rrep = getdsr_rreq(ip), getdsr_rrep(ip) rerr, srcroute = getdsr_rerr(ip), getdsr_srcroute(ip) isforme, isbcast = (dst==addr), (dst==bcast) unicast, promiscuous = (not isbcast), self.promiscuous # error check options dsrok = True if srcroute: path = srcroute.addresses segsleft, naddr = srcroute.segsleft, len(path) dsrok &= not rreq dsrok &= not isbcast dsrok &= (segsleft>0) and (segsleft<naddr+1) dsrok &= not (isforme and (not promiscuous)) if dsrok and (not promiscuous): if incoming: # incoming -> I am current hop? currhop = path[-segsleft] dsrok &= (currhop==addr) else: # outgoing -> I am right before nexthop? currhop = path[-segsleft] if (currhop==path[0]): dsrok &= segsleft==naddr dsrok &= (addr==src) else: dsrok &= (segsleft<naddr) if dsrok: dsrok &= (addr==path[-(segsleft+1)]) #if exception: assert dsrok, "[DSR]: Invalid SRCROUTE option!" if exception: assert self.safe(dsrok), "[DSR]: Invalid SRCROUTE option!" if rreq: dsrok &= not srcroute dsrok &= not unicast #if exception: assert dsrok, "[DSR]: Invalid RREQ option!" if exception: assert self.safe(dsrok), "[DSR]: Invalid RREQ option!" if rrep: dsrok &= not (isbcast and (not rreq)) dsrok &= not rerr dsrok &= (len(rrep.addresses)>0) #if exception: assert dsrok, "[DSR]: Invalid RREP option!" if exception: assert self.safe(dsrok), "[DSR]: Invalid RREP option!" if rerr: dsrok &= not (isbcast and (not rreq)) dsrok &= not rrep dsrok &= (rerr.errortype==DSR_ERROR_NODE_UNREACHABLE) #if exception: assert dsrok, "[DSR]: Invalid RERR option!" if exception: assert self.safe(dsrok), "[DSR]: Invalid RERR option!" return dsrok def updateroute(self, p, path=None, chksum=True): """Update IP+DSR routing info using a particular route. :param p: IP+DSR packet. :param path: New route; if `None`, get it from route cache. :param chksum: If true, update checksum. :return: Packet with updated routing info (or `None` if no valid route). """ ip, dsr = p[IP], p[DSRPacket] # get IP/DSR/Option parameters addr, src, dst = self.address, ip.src, ip.dst if path is None: path = self.srcroute(dst) # no path found? if path is None: return None # strip old SRCROUTE dsr.options = [o for o in dsr.options if (not getdsr_srcroute(o))] # apply new route ip.ttl = len(path) + 1 if path: srcroute = DSROPT_SRCROUTE() srcroute.segsleft = len(path) srcroute.addresses = [a for a in path] dsr.options = [srcroute] + [o for o in dsr.options] # update chksum? if chksum: chksum = self.updatechksum(ip, overwrite=True) return ip def set_sendanno(self, *args, **kwargs): """Overloaded to set rate annotations in outgoing packets.""" p = Routing.set_sendanno(self, *args, **kwargs) isip = isinstance(p, Packet) and p.haslayer(IP) isdsr = isip and p.haslayer(DSRPacket) if not isdsr: return p # remove stale annotations if p.hasanno('phy-fixed-rate'): p.delanno('phy-fixed-rate') # set rate annotation rreq = getdsr_rreq(p) rate = None if rreq: rate = self.rreqrate # assume RREQ rate is higher else: rate = self.datarate if (rate is not None): p.setanno('phy-rate', rate) # use fixed-rate anno for broadcast packets if rreq: p.setanno('phy-fixed-rate', rate) return p def getproto(self, p): """Overloaded to check for DSR packets.""" if isinstance(p, Reference): p = p._deref # determine protocol type of packet if isinstance(p, DSRPacket): proto = const.IP_PROTO_DSR else: proto = Routing.getproto(self, p) return proto def issame(self, pa, pb): """See if packets have the same IP+DSR header information.""" isip = isinstance(pa,Packet) and pa.haslayer(IP) isdsr = isip and pa.haslayer(DSRPacket) if not isdsr: return False isip = isinstance(pb,Packet) and pb.haslayer(IP) isdsr = isip and pb.haslayer(DSRPacket) if not isdsr: return False ipa, dsra = pa[IP], pa[DSRPacket] ipb, dsrb = pb[IP], pb[DSRPacket] # check IP/DSR fields same = True same &= (ipa.src==ipb.src) and (ipa.dst==ipb.dst) same &= (ipa.id==ipb.id) and (ipa.proto==ipb.proto) if (ipa.chksum is not None) and (ipb.chksum is not None): same &= (ipa.chksum==ipb.chksum) same &= (dsra.length==dsrb.length) same &= (dsra.nextheader==dsrb.nextheader) same &= (len(dsra.options)==len(dsrb.options)) return same ############################## # ROUTE TABLE METHODS ############################## def srcroute(self, *args, **kwargs): """Get source route from route cache.""" return self.table.srcroute(*args, **kwargs) def removelink(self, a, b): """Remove link from route cache.""" return self.table.removelink(a, b, self.address) ############################## # PROPERTY METHODS ############################## def get_mac(self): """Get MAC corresponding to this module.""" if isinstance(self.__mac, MAC): return self.__mac # otherwise try to get MAC from ARP mac, arp = None, self.TXD.target.parent if isinstance(arp, ARP): if self in arp.listen: mac, f = arp.listen[self] # final check on MAC if not isinstance(mac, MAC): mac = None return mac def set_mac(self, mac): """Set MAC corresponding to this module.""" m = None if isinstance(mac, MAC): m = mac self.__mac = m def get_promiscuous(self): """Get `MAC.promiscuous` flag.""" p = None if self.mac: p = self.mac.promiscuous return p ############################## # LOGGING METHODS ############################## def get_ip_anno(self, p): """Internal method to get relevant annotations for an IP+DSR packet.""" kwargs = Routing.get_ip_anno(self, p) kwargs.update(self.get_dsr_anno(p) ) kwargs.update(self.get_agt_anno(p) ) return kwargs def get_agt_anno(self, p): """Internal method to get relevant annotations for a AGT packet.""" kwargs = {} isagt = isinstance(p, Packet) and p.haslayer(AGT) if not isagt: return kwargs agt = p[AGT] kwargs['agt-root'] = agt.traceid return kwargs def get_dsr_anno(self, p): """Internal method to get relevant annotations for a DSR packet.""" kwargs = {} isdsr = isinstance(p, Packet) and p.haslayer(DSRPacket) if not isdsr: return kwargs dsr = p[DSRPacket] kwargs['dsr.nextheader'] = dsr.nextheader # classify based on DSR option rreq, rrep = getdsr_rreq(dsr), getdsr_rrep(dsr) rerr, srcroute = getdsr_rerr(dsr), getdsr_srcroute(dsr) # get parameters from options if rreq: kwargs['rreq.ID'] = rreq.identification kwargs['rreq.target'] = rreq.target kwargs['rreq.addresses'] = rreq.addresses if rrep: kwargs['rrep.addresses'] = rrep.addresses if srcroute: kwargs['srcroute.segsleft'] = srcroute.segsleft kwargs['srcroute.addresses'] = srcroute.addresses if rerr: kwargs['rerr.errortype'] = _dsr_error_types[rerr.errortype] kwargs['rerr.err_src'] = rerr.err_src kwargs['rerr.err_dst'] = rerr.err_dst kwargs['rerr.type_specific'] = rerr.type_specific # get other parameters if p.hasanno('phy-rate'): kwargs['phy-rate'] = p.getanno('phy-rate') return kwargs def debug(self, *args, **kwargs): """Log debug statements to trace.""" if DSRDEBUG: self.log(*args, **kwargs) def safe(self, x): """Print trace to standard out before throwing exception.""" if not x: self.trace.output() return x
class ARF(DCF): """Auto Rate Fallback (ARF) protocol. Implementation based on description from Kamerman and Monteban (1997). By default ARF operates using CSMA/CA (i.e. not MACAW). To enable RTS/CTS messages, set `usecsma` to False. ARF Timeout =========== The ARF timer is used to indicate that the rate of transmissions should be increased prior to having `nsuccess` consecutive ACKs. This mechanism is meant to allow ARF to explore higher data rates when traffic is sparse. The timer can be implemented as a time-based or packet-based timer. The time-based timer is a straight forward timeout timer that waits for a duration before firing and indicating that the rate of the transmission should be increased. The packet-based timer uses the "virtual timer" approach described by Qiao, Choi, and Shin (2002). This "timer" counts the number of transmission attempts made rather than using a time-based timer. This module implements the ARF timer as a time-based timer. :CVariables: * `base`: Property to access base rate enumeration from `ra`. * `rates`: Property to access ordered list of valid rate enumerations. This is ordered based on the data rates reported by `get_datarate()`. :IVariables: * `ra`: `RateAdapt` module. * `ackcount`: Dictionary containing number of consecutive successful ACKs received (negative values indicate consecutive dropped ACKs). Values are keyed by destination address. * `arftimer`: Dictionary containing ARF timers used for updating transmit data rate. Values are keyed by destination address. This module implements ARF timers as packet-based timers. * `probation`: Dictionary containing probation flags indicating if probation period has been entered; values are keyed by destination address. * `nsuccess`: Threshold for number of consecutive ACKs that must be received prior to increasing the data rate. * `nfailure`: Threshold for number of consecutive ACK failures that will trigger a decrease in the data rate. * `timeout`: Timeout value for ARF timer; if exceeded, then increase rate. """ name = "Auto Rate Fallback" tracename = "ARF" def __init__(self, usecsma=True, **kwargs): """Constructor.""" # create ARF parameters self.nsuccess = None self.nfailure = None self.timeout = None self.ackcount = {} self.arftimer = {} self.probation = {} self._rates = None # create ARF events self.dropack = SimEvent() self.recvack = SimEvent() DCF.__init__(self, usecsma=usecsma, **kwargs) # update event names self.dropack.name = "%s%s"%(self.name, ".dropack") self.recvack.name = "%s%s"%(self.name, ".recvack") base = property(fget=lambda self: self.ra.base) rates = property(fget=lambda self: self.get_rates() ) def configure(self, base=None, nsuccess=None, nfailure=None, timeout=None, **kwargs): """Configure rate adaptation parameters. :param base: Base rate used to initialize `RateAdapt` component [default=0]. :param nsuccess: Threshold for number of consecutive ACKs that must be received prior to increasing the data rate. :param nfailure: Threshold for number of consecutive ACK failures that will trigger a decrease in the data rate. :param timeout: Timeout that can automatically trigger """ DCF.configure(self, **kwargs) if base is None: base = 0 if nsuccess is None: nsuccess = ARF_NSUCCESS if nfailure is None: nfailure = ARF_NFAILURE if timeout is None: timeout = ARF_TIMEOUT ra = self.newchild("ra", RateAdapt, base=base, tracename=self.tracename+".RA") ra.set_rate(self.broadcast) # use default base rate for broadcast # create FSM to manage ARF fsm = self.newchild("manager", FSM, tracename=self.tracename+".MGR") fsm.goto(self.MGR) # set other parameters self.nsuccess = nsuccess self.nfailure = nfailure self.timeout = timeout self.ackcount = {} self.arftimer = {} self.probation = {} def MGR(self, fsm): """MGR state; manage ARF protocol.""" yield waitevent, fsm, (self.dropack, self.recvack) dropack = (self.dropack in fsm.eventsFired) recvack = (self.recvack in fsm.eventsFired) # get destination parameter from event if dropack: dst = self.dropack.signalparam if recvack: dst = self.recvack.signalparam errmsg = "[ARF]: Invalid state transition!" assert (dropack ^ recvack), errmsg # initialize ackcount if needed if (dst not in self.ackcount): self.ackcount[dst] = 0 self.probation[dst] = False self.arftimer[dst] = 0 nack = self.ackcount[dst] probation = self.probation[dst] timer = 0 # check probation condition errmsg = "[ARF]: ACK count must be zero in probation!" assert not ((nack<>0) and probation), errmsg # increment arftimer for every transmission attempt (packet-based timer) timer += 1 # process drop ACK or recv ACK if (nack>0): if dropack: nack = -1 elif recvack: nack += 1 elif (nack<0): if dropack: nack -= 1 elif recvack: nack = +1 else: if dropack: nack = -1 elif recvack: nack = +1 # probation? dropack -> decrease rate and continue using lower rate if probation and dropack: nack, timer, rate = 0, 0, self.decrate(dst) self.log("DECRATE", rate=rate, dst=dst, ackcount=nack, timer=timer, probation=probation) probation = False # reset flag # log events if dropack: self.log("DROPACK", dst=dst, ackcount=nack, arftimer=timer) if recvack: self.log("RECVACK", dst=dst, ackcount=nack, arftimer=timer) # check if Nsuccess or Nfailure condition is met timerexpired = (timer>self.timeout) if (self.nsuccess-nack<1) or timerexpired: # (Nack >= +Nsuccess) OR (Timer expired) # -> increase rate, enter probation period rate = self.incrate(dst) nack, timer = 0, 0 # reinitialize ackcount and timer probation = True # set probation flag self.log("INCRATE", rate=rate, dst=dst, ackcount=nack, probation=True) elif (nack+self.nfailure<1): # Nack <= -Nfailure -> decrease rate rate = self.decrate(dst) nack, timer = 0, 0 # set ackcount to 0, reset timer self.log("DECRATE", rate=rate, dst=dst, ackcount=nack, probation=probation) # set ackcount/probation value self.ackcount[dst] = nack self.arftimer[dst] = timer self.probation[dst] = probation # continue in MGR yield fsm.goto(self.MGR) def retry(self, *args, **kwargs): """Overloaded to manage dropped ACKs.""" rc = self.retrycount DCF.retry(self, *args, **kwargs) # Actual retry? if (self.retrycount>rc) and self.isdot11data(self.datatosend): # retry count was increased -> dropped ACK data = self.get_dot11data(self.datatosend) dst, src = data.addr1, data.addr2 #self.log("DROPACK", dst=dst, src=src) self.dropack.signal(dst) def send_data(self, data): """Additional processing for outgoing DATA.""" DCF.send_data(self, data) dst = data.addr1 rate = self.ra.get_rate(dst) data.setanno('phy-rate', rate) def recv_ack(self, ack): """Additional processing for incoming ACK.""" DCF.recv_ack(self, ack) # Expecting ACK? if self.isdot11data(self.datatosend): data = self.get_dot11data(self.datatosend) dst, src = data.addr1, data.addr2 # ACK for me? -> received ACK if (src==ack.addr1): #self.log("RECVACK", ack, dst=dst, src=src) self.recvack.signal(dst) def incrate(self, dst): """Increase data rate corresponding to destination `dst`.""" rate = self.ra.get_rate(dst) errmsg = "[ARF]: Invalid rate (%s) in incrate()!"%(rate) assert (rate in self.rates), errmsg idx = self.rates.index(rate) if (idx+1<len(self.rates)): rate = self.rates[idx+1] self.ra.set_rate(dst, rate) return rate def decrate(self, dst): """Decrease data rate corresponding to destination `dst`.""" rate = self.ra.get_rate(dst) errmsg = "[ARF]: Invalid rate (%s) in incrate()!"%(rate) assert (rate in self.rates), errmsg idx = self.rates.index(rate) if (idx>0): rate = self.rates[idx-1] self.ra.set_rate(dst, rate) return rate def get_rates(self, force=False): """Get list of valid rate enumerations. :param force: If true, recalculate rates; otherwise use cached value [default=False]. :return: List of valid rate enumerations. """ if force: self._rates = None # initialize ordered rates if self._rates is None: orates = [] # ordered rates rates = [r for r in self.phy.rate] while rates: maxrate, maxidx = None, None for k in range(len(rates)): r = rates[k] rbps = self.get_datarate(r) if maxrate is None: maxrate, maxidx = r, k mbps = self.get_datarate(maxrate) if (rbps>mbps): maxrate, maxidx = r, k orates.insert(0, rates.pop(maxidx)) # add max rate to orates self._rates = orates return self._rates def log_send(self, p, *args, **kwargs): """Updated to print `ARF` related parameters.""" if p.hasanno('phy-rate'): kwargs['phy-rate'] = p.getanno('phy-rate') DCF.log_send(self, p, *args, **kwargs) def log_recv(self, p, *args, **kwargs): """Updated to print `ARF` related parameters.""" if p.hasanno('phy-rate'): kwargs['phy-rate'] = p.getanno('phy-rate') DCF.log_recv(self, p, *args, **kwargs)
class StorageNode(IDable, Thread, RTI): """Base storage node.""" def __init__(self, cnode, ID, configs): IDable.__init__(self, '%s/sn%s' % (cnode.ID.split('/')[0], ID)) Thread.__init__(self) RTI.__init__(self, self.ID) self.logger = logging.getLogger(self.__class__.__name__) self.system = cnode.system self.configs = configs self.cnode = cnode self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024) self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread') self.groups = {} #{gid : group} self.newTxns = [] self.txnsRunning = set([]) self.shouldClose = False self.monitor = Profiler.getMonitor(self.ID) self.M_POOL_WAIT_PREFIX = '%s.pool.wait' % self.ID self.M_TXN_RUN_PREFIX = '%s.txn.run' % self.ID self.M_NUM_TXNS_RUN_KEY = '%s.num.txns' % self.ID self.runningThreads = set([]) self.closeEvent = SimEvent() self.newTxnEvent = SimEvent() @property def load(self): return len(self.txnsRunning) + len(self.newTxns) def close(self): self.logger.info('Closing %s at %s' % (self, now())) self.shouldClose = True self.closeEvent.signal() def onTxnArrive(self, txn): self.newTxns.append(txn) self.newTxnEvent.signal() def onTxnsArrive(self, txns): self.newTxns.extend(txns) self.newTxnEvent.signal() def newTxnRunner(self, txn): class DefaultTxnRunner(Thread): def __init__(self, snode, txn): Thread.__init__(self) self.snode = snode self.txn = txn self.logger = logging.getLogger(self.__class__.__name__) def run(self): self.logger.debug('Running transaction %s at %s' % (txn.ID, now())) yield hold, self, RandInterval.get('expo', 100).next() return DefaultTxnRunner(self, txn) class TxnStarter(Thread): def __init__(self, snode, txn): Thread.__init__(self) self.snode = snode self.txn = txn def run(self): #add self and txn to snode self.snode.runningThreads.add(self) #wait for pool thread resource if necessary #self.snode.logger.debug( # '%s start txn=%s, running=%s, outstanding=%s' # %(self.snode, self.txn.ID, # '(%s)'%(','.join([t.ID for t in self.snode.txnsRunning])), # '(%s)'%(','.join([t.ID for t in self.snode.newTxns])) # )) self.snode.monitor.start( '%s.%s' % (self.snode.M_POOL_WAIT_PREFIX, self.txn.ID)) yield request, self, self.snode.pool self.snode.monitor.stop( '%s.%s' % (self.snode.M_POOL_WAIT_PREFIX, self.txn.ID)) #start runner and wait for it to finish thread = self.snode.newTxnRunner(self.txn) assert self.txn not in self.snode.txnsRunning, \ '%s already started txn %s'%(self.snode, self.txn) self.snode.txnsRunning.add(self.txn) self.snode.monitor.observe(self.snode.M_NUM_TXNS_RUN_KEY, len(self.snode.txnsRunning)) self.snode.monitor.start( '%s.%s' % (self.snode.M_TXN_RUN_PREFIX, self.txn.ID)) thread.start() yield waitevent, self, thread.finish self.snode.monitor.stop('%s.%s' % (self.snode.M_TXN_RUN_PREFIX, self.txn.ID)) yield release, self, self.snode.pool #clean up self.snode.txnsRunning.remove(self.txn) self.snode.runningThreads.remove(self) self.snode.cnode.onTxnDepart(self.txn) def run(self): #the big while loop while True: yield waitevent, self, self.newTxnEvent while len(self.newTxns) > 0: #pop from new txn to running txn txn = self.newTxns.pop(0) #start thread = StorageNode.TxnStarter(self, txn) thread.start()
def __init__(self, name): Process.__init__(self, name=name) self.doneSignal = SimEvent()
class NAVTimer(Element): """Provides API to maintain network allocation vector and associated virtual carrier sense timer. :CVariables: * `timer`: Internal `Timer` object. * `done`: SimPy Event signalled when NAV timer successfully expires. * `kill`: SimPy Event signalled when NAV timer stops/pauses unexpectedly. * `fired`: Check if NAV timer (i.e. `timer`) has fired. * `running`: Check if NAV timer is still running. * `stopped`: Check if NAV timer has been stopped. """ name = "nav timer" tracename = "NAV" def __init__(self, **kwargs): """Constructor.""" self.__timer = None Element.__init__(self, **kwargs) timer = property(fget=lambda self: self.__timer) done = property(fget=lambda self: self.timer.done) kill = property(fget=lambda self: self.timer.kill) fired = property(fget=lambda self: \ isinstance(self.timer,Timer) and self.timer.fired) running = property(fget=lambda self: \ isinstance(self.timer,Timer) and self.timer.running) stopped = property(fget=lambda self: \ isinstance(self.timer,Timer) and self.timer.stopped) def configure(self, **kwargs): """Initialize `timer` to None and create monitor.""" self.__timer = None self.__wakeup = SimEvent(name=self.name+".wake") mon = self.newchild("mon", FSM, tracename=self.tracename+".MON") #mon.goto(self.MON) mon.goto(self.MONIDLE) def update(self, proc, t=None): """Blocking call to cancel active timer, if needed, and starts a new timer. :param t: New timer value. :return: Blocking clause. If `t` is None, this method will just cancel any active timer and reset `timer` to None. """ # cancel active timer if isinstance(self.timer, Timer): self.timer.halt() #self.delchild("navtimer") self.__timer = None # start new timer if (t>0): self.__timer = self.newchild("navtimer", Timer, t, start=True, \ tracename=self.tracename+".TIMER") self.__wakeup.signal(self.timer) return hold, proc, 0 def reset(self): """Non-blocking call to cancel active timer.""" return self.update(None, None) def MONIDLE(self, fsm): """MONIDLE state; monitor `timer` when NAV Timer is in IDLE state.""" # wait for timer to be set self.log_idle() yield waitevent, fsm, self.__wakeup t = self.__wakeup.signalparam assert isinstance(t, Timer) yield fsm.goto(self.MONBUSY, t) def MONBUSY(self, fsm, t): """MONIDLE state; monitor `timer` when NAV Timer is in BUSY state.""" self.log_busy(duration=time2usec(t.duration) ) yield waitevent, fsm, (t.done, t.kill) # timer fired if (t.done in fsm.eventsFired): pass # timer stopped elif (t.kill in fsm.eventsFired) and t.stopped: pass # otherwise -> raise exception else: raise RuntimeError, "[NAVTIMER]: Monitor indicates " + \ "NAV timer paused unexpectedly!" # done monitoring timer yield fsm.goto(self.MONIDLE) def MON(self, fsm): """MON state; monitor `timer` events.""" while fsm.active(): # wait for timer to be set self.log_idle() yield waitevent, fsm, self.__wakeup t = self.__wakeup.signalparam # monitor timer events while isinstance(t, Timer): self.log_busy(duration=time2usec(t.duration) ) yield waitevent, fsm, (t.done, t.kill) # timer fired if (t.done in fsm.eventsFired): pass # timer stopped elif (t.kill in fsm.eventsFired) and t.stopped: pass # otherwise -> raise exception else: raise RuntimeError, "[NAVTIMER]: Monitor indicates " + \ "NAV timer paused unexpectedly!" # done monitoring timer t = None return def log_busy(self, **kwargs): """Log NAV busy.""" self.log("NAVBUSY", **kwargs) def log_idle(self, **kwargs): """Log NAV idle.""" self.log("NAVIDLE", **kwargs) def log(self, evt=None, p=None, *args, **kwargs): """Overloaded to check verbose level and set common annotations.""" force = False if ('verbose' in kwargs): force = (kwargs['verbose']>NAV_VERBOSE) if self.verbose>NAV_VERBOSE or force: Element.log(self, evt, p, *args, **kwargs)
class Timer(FSM): """Stopwatch timer that fires an event after waiting for a specified time. After starting `Timer`, use `pause()`, `resume()`, and `stop()` to control the execution of the timer. The `done` and `kill` signals to monitor the completion of the `Timer`. :CVariables: * `CMDPAUSE`: Internal constant for pause command. * `CMDRESUME`: Internal constant for resume command. * `duration`: Property to access time that `Timer` should run. * `timepassed`: Property to access time elapsed by `Timer`. * `timeleft`: Property to access time left on `Timer`. * `ispaused`: Property to check if timer is currently paused. * `fired`: Property to check if `done` occurred. * `running`: Property to check if `started` and currently running. * `stopped`: Property to check if not `fired` and no longer active. * `ctrlQ`: Internal `Queue` for control messages. :IVariables: * `done`: SimEvent signalled when timer finishes successfully. * `kill`: SimEvent signalled when timer is prematurely stopped. """ name="timer" tracename="TIMER" CMDPAUSE = "pause" CMDRESUME = "resume" def __init__(self, duration, start=False, initstate=None, **kwargs): """Constructor. :param duration: Time for which `Timer` should run. :param start: Boolean; if true, `start()` immediately. :param initstate: Depricated (do not use). :param kwargs: Keywords passed to `FSM` constructor. """ assert (duration>0), "[TIMER]: Cannot simulate non-positive duration!" FSM.__init__(self, start=False, initstate=self.RUN, **kwargs) self.__tpassed = 0 self.__duration = duration self.__ctrlQ = Queue() self.__tic = None self.done = SimEvent(name=self.name+".done") self.kill = SimEvent(name=self.name+".kill") if start: self.start() duration = property(fget=lambda self: self.__duration) timepassed = property(fget=lambda self: self.__timepassed() ) timeleft = property(fget=lambda self: self.duration - self.timepassed) ispaused = property(fget=lambda self: (self.state==self.PAUSE) ) fired = property(fget=lambda self: self.done.occurred) running = property(fget=lambda self: \ self.started and (self.state==self.RUN) ) stopped = property(fget=lambda self: \ (not self.fired) and (self.state==self.HALT) ) ctrlQ = property(fget=lambda self: self.__ctrlQ) def RUN(self, fsm): """RUN state; timer is active. Call `pause()` to pause an active timer. This method will signal `done` upon completion. """ # set parameters tstart = now() self.__tic = tstart # temporarily store tic as start of RUN queue = self.ctrlQ tleft = self.duration - self.__tpassed # time left on timer # wait for timer to expire (or be paused) yield queue.remove(fsm, 1, renege=tleft) self.__tic = None telapsed = now() - tstart self.__tpassed += telapsed if fsm.acquired(queue): # PAUSE command assert (telapsed<tleft), \ "[TIMER]: Elapsed time exceeded time left during RUN!" assert (len(fsm.got)==1), "[TIMER]: Control queue failed!" cmd = fsm.got[0] assert (cmd==self.CMDPAUSE), \ "[TIMER]: Invalid control command received in RUN!" yield fsm.goto(self.PAUSE) else: assert (abs(self.__tpassed-self.duration)<const.EPSILON), \ "[TIMER]: Timer failed to complete properly in RUN!" self.__tpassed = self.duration if self.verbose>TIMER_VERBOSE: self.log("DONE") self.done.signal() yield fsm.goto(self.HALT, force=False) def PAUSE(self, fsm): """PAUSE state; timer is paused. Call `resume()` to restart timer. """ queue = self.ctrlQ yield queue.remove(fsm, 1) assert fsm.acquired(queue) and (len(fsm.got)==1), \ "[TIMER]: PAUSE failed to dequeue control message!" cmd = fsm.got[0] assert (cmd==self.CMDRESUME), \ "[TIMER]: Invalid control command received in PAUSE!" yield fsm.goto(self.RUN) def resume(self, proc): """Blocking call to restart timer if in `PAUSE` state. :param proc: Process to block on resume command. """ if (self.state==self.PAUSE): return self.ctrlQ.insert(proc, [self.CMDRESUME]) else: return hold, proc, 0 def pause(self, proc): """Blocking call to pause timer if in `RUN` state. :param proc: Process to block on pause command. """ if (self.state==self.RUN): return self.ctrlQ.insert(proc, [self.CMDPAUSE]) else: return hold, proc, 0 def __timepassed(self): """Private method to determine time elapsed on timer.""" if self.__tic is None: return self.__tpassed else: tdelta = now() - self.__tic return (self.__tpassed + tdelta) def HALT(self, fsm, force=True): """Overload `HALT` state to signal `kill` if needed.""" if (force or (self.timeleft>0)): timepassed = self.timepassed if self.verbose>TIMER_VERBOSE: self.log("CANCEL", timepassed=time2usec(timepassed, fmt="%.4g"), \ timeleft=self.timeleft, force=force) self.kill.signal(timepassed) yield hold, fsm, 0 def log(self, evt=None, p=None, *args, **kwargs): """Overloaded to check verbose level and set common annotations.""" force = False if ('verbose' in kwargs): force = (kwargs['verbose']>TIMER_VERBOSE) if self.verbose>TIMER_VERBOSE or force: FSM.log(self, evt, p, *args, **kwargs)