periodic.py
Go to the documentation of this file.
00001 #!/bin/env python
00002 
00003 #**********************************************************************
00004 # Copyright (c) 2013 InterWorking Labs, All Rights Reserved           *
00005 #                                                                     *
00006 #              RESTRICTED RIGHTS LEGEND                               *
00007 #                                                                     *
00008 # This software is provided with RESTRICTED RIGHTS.                   *
00009 #                                                                     *
00010 # Use, duplication, or disclosure by the U.S. Government is subject   *
00011 # to restrictions set forth in subparagraph (c)(1)(ii) of the Rights  *
00012 # in Technical Data and Computer Software clause at DFARS             *
00013 # 252.227-7013 or subparagraphs (c)(1) and (2) of the Commercial      *
00014 # Computer Software - Restricted Rights at 48 CFR 52.227-19, as       *
00015 # applicable.  The "Manufacturer" for purposes of these regulations   *
00016 # is InterWorking Labs, PO Box 66190, Scotts Valley, California 95060 *
00017 # U.S.A.                                                              *
00018 #**********************************************************************
00019 
00020 #**********************************************************************
00021 # $Id: periodic.py 462 2013-11-17 08:50:51Z karl $
00022 #**********************************************************************
00023 
00024 # http://stackoverflow.com/questions/2398661/schedule-a-repeating-event-in-python-3
00025 
00026 import argparse
00027 import datetime
00028 import sys
00029 import syslog
00030 import threading
00031 import time
00032 import traceback
00033 
00034 import mm2client
00035 import setfilters
00036 
00037 #**********************************************************************
00038 # SetupJobs()
00039 #
00040 # Here is where we set up the jobs to be done and the schedule to
00041 # do them.
00042 #**********************************************************************
00043 def SetupJobs(Jobs, mm_hostname):
00044 
00045     #******************************
00046     # Do not change these...
00047     LANA_to_LANB = True
00048     LANB_to_LANA = False
00049     #******************************
00050 
00051     BandSettings = mm2client.Bands()
00052 
00053     # A Band is for our 100ms/1megabit path
00054     # B band for the 1000ms/1megabit path
00055     A_Band = 1
00056     B_Band = 2
00057 
00058     #**************************************************
00059     # Here we set rate limitation value that we will use
00060     #**************************************************
00061     OneMegabit = 1000000        # Some might argue that one megabit is 1048576 (2**20)
00062     OneKilobit = 100000         # Some might argue that one kilobit is 1024000
00063 
00064     #**************************************************
00065     # Here we set the two delay values.
00066     # Note: These are round trip times.
00067     #**************************************************
00068     LongRoundtrip = 1000
00069     ShortRoundtrip = 100
00070 
00071     # We will divide the round trip into two equal one-way parts.
00072     HalfLongRoundtrip = LongRoundtrip/2
00073     HalfShortRoundtrip = ShortRoundtrip/2
00074 
00075     #**************************************************
00076     # Set up our classification filters.
00077     # We will be using these as a two-way switch to
00078     # send selected packets first into the A band and then
00079     # into the B band and then back to the A band etc.
00080     # We will let any other forms of traffic flow via
00081     # Band 5 which will get no impairments.
00082     # There things that don't get picked up by the
00083     # filters below are almost certainly low traffic
00084     # things like OSPF.
00085     #**************************************************
00086     Filt_ToBandA = [setfilters.FiltSetting("arp", A_Band),
00087                     setfilters.FiltSetting("ipv4", A_Band),
00088                     setfilters.FiltSetting("ipv6", A_Band)
00089                     ]
00090 
00091     Filt_ToBandB = [setfilters.FiltSetting("arp", B_Band),
00092                     setfilters.FiltSetting("ipv4", B_Band),
00093                     setfilters.FiltSetting("ipv6", B_Band)
00094                     ]
00095 
00096     # Define A_Band to have 100ms round trip, 1megabit/second rate limit
00097     BandSettings.SetDelayAmount(A_Band, LANA_to_LANB, HalfShortRoundtrip)
00098     BandSettings.SetDelayAmount(A_Band, LANB_to_LANA, HalfShortRoundtrip)
00099     BandSettings.SetDelayReorder(A_Band, LANA_to_LANB, False)
00100     BandSettings.SetDelayReorder(A_Band, LANB_to_LANA, False)
00101     BandSettings.SetRateLimit(A_Band, LANA_to_LANB, OneMegabit)
00102     BandSettings.SetRateLimit(A_Band, LANB_to_LANA, OneMegabit)
00103 
00104     # Define B_Band with 1000ms round trip, 1kilobit/second rate limit
00105     BandSettings.SetDelayAmount(B_Band, LANA_to_LANB, HalfLongRoundtrip)
00106     BandSettings.SetDelayAmount(B_Band, LANB_to_LANA, HalfLongRoundtrip)
00107     BandSettings.SetDelayReorder(B_Band, LANA_to_LANB, False)
00108     BandSettings.SetDelayReorder(B_Band, LANB_to_LANA, False)
00109     BandSettings.SetRateLimit(B_Band, LANA_to_LANB, OneKilobit)
00110     BandSettings.SetRateLimit(B_Band, LANB_to_LANA, OneKilobit)
00111 
00112     #**************************************************
00113     # Create an initial job at start-time zero.
00114     # Here is where we establish our baseline.
00115     # Note that this baseline is a different thing than
00116     # the defaults, which are simply the absence of
00117     # filters and impairments.
00118     #**************************************************
00119     Jobs.AddRequest("Establish Baseline - 100ms RT delay, 1,000,000 bit/sec rate limit", mm_hostname, 0,
00120                     BandSettings, Filt_ToBandA, Filt_ToBandA)
00121 
00122     #**************************************************
00123     # Set up the repeating schedule of jobs
00124     #**************************************************
00125     interval = 60       # Seconds per item
00126     items_per_group = 2
00127     num_groups = 720    # Number of groups ==> 24 hours
00128 
00129     #Remember range() stops at < stop value not <=
00130     for secs in range(interval, items_per_group*num_groups*interval, items_per_group*interval):
00131         Jobs.AddRequest("Job %u: 1000ms RT delay, 100,000 bit/sec rate limit" % \
00132                             secs, mm_hostname, secs, None, Filt_ToBandB, Filt_ToBandB)
00133         nxtsecs = secs + interval
00134         Jobs.AddRequest("Job %u: 100ms RT delay, 1,000,000 bit/sec rate limit" % \
00135                             nxtsecs, mm_hostname, nxtsecs, None, Filt_ToBandA, Filt_ToBandA)
00136     #Jobs.PrintMe()
00137 
00138 #**********************************************************************
00139 # ShowMessage()
00140 #**********************************************************************
00141 UseSyslog = False # Set to True to send things to the syslog
00142 TeamName = "TEAM UNKNOWN" # Should be set to the actual team name
00143 
00144 def ShowMessage(*args):
00145     datetime.datetime.now().isoformat()
00146     msg = datetime.datetime.now().isoformat() \
00147           + ' "' + TeamName + '" ' \
00148           + " ".join(args)
00149     print msg
00150     if UseSyslog:
00151         syslog.syslog(syslog.LOG_INFO, msg)
00152 
00153 #**********************************************************************
00154 # RepeatedTimer
00155 #
00156 # Features:
00157 #  - Standard library only, no external dependencies.
00158 #  - Start() and stop() are safe to call multiple times even if the
00159 #    timer has already started/stopped.
00160 #  - Function to be called can have positional and named arguments.
00161 #  - You can change interval anytime, it will be effective after
00162 #    next run. Same for args, kwargs and even the function.
00163 #**********************************************************************
00164 class RepeatedTimer(object):
00165     def __init__(self, interval, function, *args, **kwargs):
00166         self._timer     = None
00167         self.function   = function
00168         self.interval   = interval
00169         self.args       = args
00170         self.kwargs     = kwargs
00171         self.is_running = False
00172         self.start()
00173 
00174     def _run(self):
00175         self.is_running = False
00176         self.start()
00177         self.function(*self.args, **self.kwargs)
00178 
00179     def start(self):
00180         if not self.is_running:
00181             self._timer = threading.Timer(self.interval, self._run)
00182             self._timer.start()
00183             self.is_running = True
00184 
00185     def stop(self):
00186         self._timer.cancel()
00187         self.is_running = False
00188         
00189 
00190 #**********************************************************************
00191 # DoRequest
00192 #**********************************************************************
00193 class DoRequest(object):
00194     def __init__(self, reqname, mm2host, dowhen, bands, a2b_filtmap, b2a_filtmap):
00195         """
00196         @param reqname: The name of the request, a single line of text.
00197         @type reqname: String
00198         @param mm2host: The name or IP address of the target host.
00199         @type mm2host: String
00200         @param dowhen: A datetime object indicating when to do perform this request.
00201         @type dowhen: datetime
00202         @param bands: A mm2client.Bands object containing the desired impairment settings.
00203                       May be None.
00204         @type bands: mm2client.Bands
00205         @param a2b_filtmap: A sequence of setfilters.FiltSetting objects containing the desired LanA to LanB filters settings.
00206                             May be an empty sequence.
00207         @type a2b_filtmap: A sequence of setfilters.FiltSetting objects.
00208         @param b2a_filtmap: A sequence of setfilters.FiltSetting objects containing the desired LanB to LanA filters settings.
00209                             May be an empty sequence.
00210         @type b2a_filtmap: A sequence of setfilters.FiltSetting objects.
00211         """
00212         self.__Name = reqname
00213         self.__MM2HostName = mm2host
00214         self.__DoWhen = dowhen
00215         self.__Bands = bands
00216         self.__A2BFiltmap = a2b_filtmap
00217         self.__B2AFiltmap = b2a_filtmap
00218         self.__Done = False
00219 
00220     def __lt__(self, other):
00221         return self.__DoWhen < other.__DoWhen
00222     
00223     def __le__(self, other):
00224         return self.__DoWhen <= other.__DoWhen
00225 
00226     def __eq__(self, other):
00227         return self.__DoWhen == other.__DoWhen
00228 
00229     def __ne__(self, other):
00230         return self.__DoWhen != other.__DoWhen
00231 
00232     def __gt__(self, other):
00233         return self.__DoWhen > other.__DoWhen
00234 
00235     def __ge__(self, other):
00236         return self.__DoWhen <= other.__DoWhen
00237 
00238     def __repr__(self):
00239         return repr({
00240                 "Name": self.__Name,
00241                 "MM2HostName": self.__MM2HostName,
00242                 "DoWhen": self.__DoWhen,
00243                 "Bands": repr(self.__Bands),
00244                 "A2BFiltMap": repr(self.__A2BFiltmap),
00245                 "B2AFiltMap": repr(self.__B2AFiltmap),
00246                 "Done": self.__Done
00247                 })
00248 
00249     def __str__(self):
00250         return self.__repr__()
00251 
00252     @property
00253     def Name(self):
00254         return self.__Name
00255 
00256     @property
00257     def MM2HostName(self):
00258         return self.__MM2HostName
00259 
00260     @property
00261     def DoWhen(self):
00262         return self.__DoWhen
00263 
00264     @property
00265     def Bands(self):
00266         return self.__Bands
00267 
00268     @property
00269     def A2BFiltmap(self):
00270         return self.__A2BFiltmap
00271 
00272     @property
00273     def B2AFiltmap(self):
00274         return self.__B2AFiltmap
00275 
00276     @property
00277     def IsDone(self):
00278         return self.__Done
00279 
00280     def IsReadyToGo(self, now):
00281         return (not self.__Done) and (self.__DoWhen <= now)
00282 
00283     def Run(self, now):
00284         global AllFilterNames
00285         assert self.IsReadyToGo(now)
00286         self.__Done = True
00287         SetMM(self.__MM2HostName, self.__Bands, 
00288               self.__A2BFiltmap, self.__B2AFiltmap, AllFilterNames)
00289 
00290 #**********************************************************************
00291 # RunList
00292 #**********************************************************************
00293 class RunList(object):
00294     def __init__(self, now, skipsecs):
00295         self.__WorkList = []
00296         self.__StartTime = now
00297         if (skipsecs is not None) and (skipsecs > 0):
00298             self.__StartTime = self.__StartTime - datetime.timedelta(0, skipsecs)
00299 
00300     def AddRequest(self, reqname, mm2host, startsec, bands,
00301                    a2b_filtmap, b2a_filtmap):
00302         dowhen = self.__StartTime + datetime.timedelta(0, startsec)
00303         self.__WorkList.append(DoRequest(reqname, mm2host, dowhen, bands,
00304                                          a2b_filtmap, b2a_filtmap))
00305         self.__WorkList.sort()
00306 
00307     def RunNextRequest(self, nowtime):
00308         # timedelta = nowtime - self.__StartTime
00309         # We are going to do a sequential scan of the work list to
00310         # find the first one that has not already been done and
00311         # that has reached its start time.
00312         # This check happens about once a second.
00313         for ndx in range(len(self.__WorkList)):
00314             req = self.__WorkList[ndx]
00315             if req.IsReadyToGo(nowtime):
00316                 ShowMessage("Starting task \"%s\"" % req.Name)
00317                 try:
00318                     req.Run(nowtime)
00319                 except Exception, err:
00320                     ShowMessage("Exception in Task \"%s\" - %s" % (req.Name, str(err)))
00321                 del self.__WorkList[ndx]
00322                 return True
00323         return False
00324 
00325     @property
00326     def LastRequestStartTime(self):
00327         if len(self.__WorkList) == 0:
00328                return datetime.datetime.now()
00329         return self.__WorkList[-1].DoWhen
00330 
00331     def PrintMe(self):
00332         for d in self.__WorkList:
00333             ShowMessage("RunRequest", str(d))
00334 
00335 #**********************************************************************
00336 # SetMM()
00337 #**********************************************************************
00338 def SetMM(mm_hostname, bands, a2bfilt_vals, b2afilt_vals, all_filter_names):
00339 
00340     if bands is not None:
00341         mm2client.ChangeBandsOnMM(bands, mm_hostname)
00342     if (a2bfilt_vals is not None) or (b2afilt_vals is not None):
00343         setfilters.SetFiltMap(mm_hostname, a2bfilt_vals, b2afilt_vals,
00344                               all_filter_names)
00345 
00346 #**********************************************************************
00347 # We will try to avoid redundant queries to fetch filter names by
00348 # caching it.
00349 # It is simply a list of strings.
00350 # A value of None means that it will be fetched each time rather than
00351 # cached.
00352 #**********************************************************************
00353 AllFilterNames = None
00354 
00355 #**********************************************************************
00356 # main()
00357 #**********************************************************************
00358 def main():
00359     global UseSyslog
00360     global AllFilterNames
00361     global TeamName
00362 
00363     #******************************
00364     # jobtick() - Where we run the job
00365     #******************************
00366     def jobtick():
00367         Jobs.RunNextRequest(datetime.datetime.now())
00368 
00369     #******************************
00370     # Deal with the arguments
00371     #******************************
00372     parser = argparse.ArgumentParser(description="Start impairment pattern.")
00373 
00374     parser.add_argument("-s", "--skip", type=int,
00375                         help="Skip ahead: start at N seconds.")
00376 
00377     parser.add_argument("-C", "--do_not_use_initial_defaults", action="store_true",
00378                         dest="no_initial_defaults",
00379                         help="Do not establish defaults before running scheduled tasks.")
00380 
00381     parser.add_argument("-D", "--initial_defaults_then_exit", action="store_true",
00382                         dest="initial_defaults_only",
00383                         help="Establish defaults and then exit, supersedes -C.")
00384 
00385     parser.add_argument("-l", "--loop", action="store_true",
00386                         help="Loop/Cycle forever.")
00387 
00388     parser.add_argument("-S", "--syslog", action="store_true",
00389                         help="Send reports to the system defined syslog server (level LOG_INFO).")
00390 
00391     parser.add_argument("-T", "--team", required=True, metavar="<team name>",
00392                         help="Team name.")
00393 
00394     parser.add_argument("mm_hostname",  metavar="<Mini Maxwell host name or IP address>",
00395                         help='The host name or IP address of the Mini Maxwell/Maxwell G.')
00396 
00397     pargs = parser.parse_args()
00398 
00399     TeamName = pargs.team
00400     if pargs.syslog:
00401         UseSyslog = True
00402     
00403     mm_hostname = pargs.mm_hostname
00404 
00405     if AllFilterNames is None:
00406         AllFilterNames = setfilters.GetAllFilterNames(mm_hostname)
00407 
00408     #**************************************************
00409     # Do we just set the defaults and then exit?
00410     #**************************************************
00411     if pargs.initial_defaults_only:
00412         # Coerce default filter settings and impairments to a known (default) state
00413         ShowMessage("Coercing filters and impairments to default state, then exiting")
00414         SetMM(mm_hostname, mm2client.Bands(), [], [], AllFilterNames)
00415         ShowMessage("Stopping - Complete")
00416         sys.exit()
00417 
00418     #**************************************************
00419     # Set default values (unless suppressed)
00420     #**************************************************
00421     if pargs.no_initial_defaults:
00422         ShowMessage("Not setting initial defaults")
00423     else:
00424         # Coerce default filter settings and impairments to a known (default) state
00425         ShowMessage("Coercing filters and impairments to default state")
00426         SetMM(mm_hostname, mm2client.Bands(), [], [], AllFilterNames)
00427 
00428     skipsecs = pargs.skip
00429 
00430     #**************************************************
00431     # Main scheduling loop
00432     # Note: This loop does not repeat setting of the
00433     # default values.  But it does repeat the initial
00434     # (time zero) job.
00435     # Also note that any skipping of initial time is
00436     # done only on the first iteration of this loop.
00437     # When looping the first job of the new iteration
00438     # will start immediately.
00439     #**************************************************
00440     while True:
00441         StartTime = datetime.datetime.now()
00442 
00443         Jobs = RunList(StartTime, skipsecs)
00444         skipsecs = 0 # We only do the skip on the first cycle
00445 
00446         SetupJobs(Jobs, mm_hostname)
00447 
00448         # Come up to date with initial and skipped tasks.
00449         ShowMessage("Running initial and skipped tasks to establish state.")
00450         while Jobs.RunNextRequest(StartTime):
00451             pass # Yes, we want to pass, all the work is done in the RunNextRequest()
00452 
00453         ShowMessage("Beginning scheduled events")
00454 
00455         # Give ourselves a lifetime.
00456         # Add a few seconds to let the last job complete.
00457         sleepsecs = (Jobs.LastRequestStartTime - datetime.datetime.now()).total_seconds() + 5
00458 
00459         # Begin the timer system with a one second interval
00460         rt = RepeatedTimer(1, jobtick)
00461         try:
00462             ShowMessage("Start running for %u seconds." % sleepsecs)
00463             time.sleep(sleepsecs)
00464         finally:
00465             rt.stop() # better in a try/finally block to make sure the program ends!
00466 
00467         if not pargs.loop:
00468             break
00469 
00470         ShowMessage("Beginning again")
00471 
00472     ShowMessage("Stopping - Finished")
00473 
00474 #**********************************************************************
00475 # Entry
00476 #**********************************************************************
00477 if __name__ == "__main__":
00478 
00479     try:
00480         main()
00481     except Exception, err:
00482         ShowMessage("Unhandled exception:", str(err))
00483         traceback.print_exc()
00484 
00485     except KeyboardInterrupt:
00486         ShowMessage("Keyboard Interruptception")


mini_maxwell
Author(s): Yusuke Furuta
autogenerated on Sun Jan 25 2015 12:37:43