Example #1
0
File: nfpa.py Project: cslev/nfpa
    def initialize(self):

        
        #read config
        self.rc = ReadConfig(self.config_file)
        if(self.rc == -1):
            #error during reading config
            return -1
            
        self.config = self.rc.getConfig()



        self.log = l.getLogger(self.__class__.__name__,
                               self.config['LOG_LEVEL'],
                               self.config['app_start_date'],
                               self.config['LOG_PATH'])
        
        
        self.pid_file=self.config['MAIN_ROOT'] + "/" + "nfpa.pid"
        self.log.info("Deleting previous pid_file: %s" % self.pid_file)
        os.system("rm -rf " + self.pid_file)
        
        #before fresh start remove temporary files if they were not removed
        #already. This could be happen, if in some case, NFPA crashes, and
        #temporary res files in PKTGEN_ROOT/ still remains existing and can
        #influence a latter measurement results in a wrong way
        self.log.info("Clean up old .res files in PKTGEN's root dir...")
        self.deleteResFiles()
        self.log.info("[DONE]")

        #create a tmp directory for flow rules under nfpa/of_rules
        path=self.config["MAIN_ROOT"] + "/of_rules/tmp"
        if not os.path.exists(path):
            os.makedirs(path)

        self.log.debug("tmp directory created under of_rules")
        
        
        self.log.info("### Measurement scenario '" + self.scenario_name + \
                      "' has been initiated ###")
        
        
        #append scenario name to self.config dictionary for later usage
        self.config['scenario_name'] = self.scenario_name

        
        self.log.info(str(self.config))
        #assembling log file path
        self.log_file_path = self.config['MAIN_ROOT'] + "/log/log_" + \
                             df.getDateFormat(self.config['app_start_date']) +\
                             ".log"

        self.log.info("Log file for this measurement is: %s" % self.log_file_path)
        self.log.info("THANKS FOR USING NFPA FOR MEASURING")

        self.storePID(str(os.getpid()))
        self.log.debug("NFPA PID stored")

        # create an instance of the EmailAdapter and store this object in self.config
        # if email service was enabled in the config file
        if self.config['email_service'].lower() == "true":
            self.config['email_adapter'] = EmailAdapter(self.config)
        else:
            self.config['email_adapter'] = None
Example #2
0
File: nfpa.py Project: cslev/nfpa
class NFPA(object):
    '''This is the main class'''
    
    def __init__(self, **kwargs):
        '''
        Constructor
         - initiate config file reading and scenario name
         kwargs.scenario_name String - name of the scenario
         
        '''
        self.config = {}
        #default name TEST
        self.scenario_name = kwargs.get("scenario_name","TEST")
        self.reset_terminal = kwargs.get("reset_terminal", True)
        self.no_database = kwargs.get("no_database", False)
        self.config_file = kwargs.get("config_file", "nfpa.cfg")
        
  
    def storePID(self, new_pid):
        '''
        This process save the new_pid variables into nfpa.pids file to be able
        to kill the whole process tree during execution
        new_pid Int - the pid to store
        '''
        
        file = open(self.pid_file,'w')
        file.write(str(new_pid))
        file.write("\n")
        file.close()
        
    def initialize(self):

        
        #read config
        self.rc = ReadConfig(self.config_file)
        if(self.rc == -1):
            #error during reading config
            return -1
            
        self.config = self.rc.getConfig()



        self.log = l.getLogger(self.__class__.__name__,
                               self.config['LOG_LEVEL'],
                               self.config['app_start_date'],
                               self.config['LOG_PATH'])
        
        
        self.pid_file=self.config['MAIN_ROOT'] + "/" + "nfpa.pid"
        self.log.info("Deleting previous pid_file: %s" % self.pid_file)
        os.system("rm -rf " + self.pid_file)
        
        #before fresh start remove temporary files if they were not removed
        #already. This could be happen, if in some case, NFPA crashes, and
        #temporary res files in PKTGEN_ROOT/ still remains existing and can
        #influence a latter measurement results in a wrong way
        self.log.info("Clean up old .res files in PKTGEN's root dir...")
        self.deleteResFiles()
        self.log.info("[DONE]")

        #create a tmp directory for flow rules under nfpa/of_rules
        path=self.config["MAIN_ROOT"] + "/of_rules/tmp"
        if not os.path.exists(path):
            os.makedirs(path)

        self.log.debug("tmp directory created under of_rules")
        
        
        self.log.info("### Measurement scenario '" + self.scenario_name + \
                      "' has been initiated ###")
        
        
        #append scenario name to self.config dictionary for later usage
        self.config['scenario_name'] = self.scenario_name

        
        self.log.info(str(self.config))
        #assembling log file path
        self.log_file_path = self.config['MAIN_ROOT'] + "/log/log_" + \
                             df.getDateFormat(self.config['app_start_date']) +\
                             ".log"

        self.log.info("Log file for this measurement is: %s" % self.log_file_path)
        self.log.info("THANKS FOR USING NFPA FOR MEASURING")

        self.storePID(str(os.getpid()))
        self.log.debug("NFPA PID stored")

        # create an instance of the EmailAdapter and store this object in self.config
        # if email service was enabled in the config file
        if self.config['email_service'].lower() == "true":
            self.config['email_adapter'] = EmailAdapter(self.config)
        else:
            self.config['email_adapter'] = None



    def exiting(self):
        '''
        This small function only prints out EXITING and call system.exit with
        ERROR status -1.
        Used only for function checkConfig()'s return values 
        '''
        self.log.error("EXITING...")
        if (self.config['email_adapter'] is not None) and \
            (not self.config['email_adapter'].sendErrorMail()):
            self.log.error("Sending ERROR email did not succeed...")
        exit(-1)
        
    def configureVNFRemote(self, vnf_function, traffictype):
        '''
        This function will configure the remote vnf via pre-installed tools
        located on the same machine where NFPA is.
        Only works for some predefined vnf_function and traffictraces

        :return: True - if success, False - if not
        '''

        #the path to the openflow rules
        of_path = self.config["MAIN_ROOT"] + "/of_rules/"
        # temporary variable for bidir status - it is needed for flow_rules_preparator
        bidir = False

        #handle here OpenFlow and setup via ovs-ofctl
        if self.config["control_vnf"].lower() == "openflow":

            # first, delete the flows
            ofctl_cmd = self.config["control_path"] + " " + \
                        self.config["control_args"] +\
                        " <C> " + \
                        self.config["control_mgmt"] + " "
            cmd = ofctl_cmd.replace("<C>", "del-flows")
            self.log.debug("control cmd: %s" % cmd)
            invoke.invoke(command=cmd,
                          logger=self.log,
                          email_adapter=self.config['email_adapter'])
            self.log.info("Flow rules deleted")

            # second, delete groups
            cmd = ofctl_cmd.replace("<C>", "del-groups")
            self.log.debug("control cmd: %s" % cmd)
            invoke.invoke(command=cmd,
                          logger=self.log,
                          email_adapter=self.config['email_adapter'])
            self.log.info("Groups deleted")

            #OK, flows are deleted, so replace 'del-flows' to 'add-flows' for
            # easier usage later
            cmd = ofctl_cmd.replace("<C>", "add-flows")
            #first check vnf_function, if it is bridge, then no special stuff needs
            #to be setup regardless of the traces
            ############     BRIDGE ###########
            if self.config["vnf_function"].lower() == "bridge":
                #add birdge rules - located under of_rules
                scenario_path = vnf_function + "_unidir.flows"
                if not (os.path.isfile(str(of_path + scenario_path))):
                    self.log.error("Missing flow rule file: %s" % scenario_path)
                    self.log.error("NFPA does not know how to configure VNF to act as a bridge")
                    self.log.error("More info: http://ios.tmit.bme.hu/nfpa")
                    if (self.config['email_adapter'] is not None) and \
                        (not self.config['email_adapter'].sendErrorMail()):
                        self.log.error("Sending ERROR email did not succeed...")
                    exit(-1)

                if self.config["biDir"] == 1:
                    #change flow rule file if bidir was set
                    scenario_path = scenario_path.replace("unidir","bidir")
                    bidir=True

                #prepare flow rule file
                scenario_path = flow_prep.prepareOpenFlowRules(self.log,
                                                               of_path,
                                                               scenario_path,
                                                               self.config["control_vnf_inport"],
                                                               self.config["control_vnf_outport"],
                                                               bidir)
                cmd = ofctl_cmd.replace("<C>","add-flows") + scenario_path
                self.log.info("add-flows via '%s'" % cmd)
                invoke.invoke(command=cmd,
                              logger=self.log,
                              email_adapter=self.config['email_adapter'])
                # print out stdout if any
                self.log.info("Flows added")
                return True
            ############    =============   ###########


            ############     OTHER CASES    ###########
            #check whether flow rules exists?
            #convention vnf_function.trace_direction.flows
            scenario_path = vnf_function + "." + traffictype + "_unidir.flows"
            if not (os.path.isfile(str(of_path + scenario_path))):
                self.log.error("Missing flow rule file: %s" % scenario_path)
                self.log.error("NFPA does not know how to configure VNF to act as " + \
                               "%s for the given trace %s" % (vnf_function,traffictype))
                self.log.error("More info: http://ios.tmit.bme.hu/nfpa")
                if (self.config['email_adapter'] is not None) and \
                    (not self.config['email_adapter'].sendErrorMail()):
                    self.log.error("Sending ERROR email did not succeed...")
                exit(-1)


            #If flow file exists try to find corresponding groups
            scenario_path = scenario_path.replace(".flows",".groups")
            self.log.info("Looking for group file: %s" % scenario_path)
            if (os.path.isfile(str(of_path + scenario_path))):
                self.log.info("Group file found for this scenario: %s" % scenario_path)
                #prepare group file, i.e., replace port related meta data
                group_path = flow_prep.prepareOpenFlowRules(self.log,
                                                               of_path,
                                                               scenario_path,
                                                               self.config["control_vnf_inport"],
                                                               self.config["control_vnf_outport"],
                                                               False) #TODO: bidir handling here
                cmd = ofctl_cmd.replace("<C>","add-groups")
                cmd += " " + group_path
                self.log.info("add-groups via '%s'" % cmd)
                invoke.invoke(command=cmd,
                              logger=self.log,
                              email_adapter=self.config['email_adapter'])
            else:
                self.log.info("No group file was found...continue")

            #change back to the .flows file from .groups
            scenario_path = scenario_path.replace(".groups", ".flows")

            #if biDir is set, then other file is needed where the same rules are present
            #in the reverse direction
            if (int(self.config["biDir"]) == 1):
                #biDir for remote vnf configuration is currently not supported!
                self.log.error("Configuring your VNF by NFPA for bi-directional scenario " +
                               "is currently not supported")
                self.log.error("Please verify your nfpa.cfg")
                if (self.config['email_adapter'] is not None) and \
                    (not self.config['email_adapter'].sendErrorMail()):
                    self.log.error("Sending ERROR email did not succeed...")
                exit(-1)
                #save biDir setting in a boolean to later use for flow_prep.prepareOpenFlowRules()
                # bidir = True
                # scenario_path=scenario_path.replace("unidir","bidir")
                # if not (os.path.isfile(str(of_path + scenario_path))):
                #     self.log.error("Missing flow rule file: %s" % scenario_path)
                #     self.log.error("NFPA does not know how to configure VNF to act as " + \
                #                    "%s for the given trace %s in bi-directional mode" %
                #                    (vnf_function,traffictype))
                #     self.log.error("More info: http://ios.tmit.bme.hu/nfpa")
                #     exit(-1)

            #replace metadata in flow rule files
            scenario_path = flow_prep.prepareOpenFlowRules(self.log,
                                                           of_path,
                                                           scenario_path,
                                                           self.config["control_vnf_inport"],
                                                           self.config["control_vnf_outport"],
                                                           bidir)
            #assemble command ovs-ofctl
            cmd = ofctl_cmd.replace("<C>","add-flows") + scenario_path
            self.log.info("add-flows via '%s'" % cmd)
            self.log.info("This may take some time...")
            invoke.invoke(command=cmd,
                          logger=self.log,
                          email_adapter=self.config['email_adapter'])
            self.log.info("Flows added")
            return True
        ############    =============   ###########


        else:
            self.log.error("Currently, only openflow is supported!")
            if (self.config['email_adapter'] is not None) and \
                (not self.config['email_adapter'].sendErrorMail()):
                self.log.error("Sending ERROR email did not succeed...")
            exit(-1)


    def startAnalyzing(self, traffic_type, traffic_trace):
        '''
        This function actually called after pktgen measurements are done, and it instantiate
        results_analyzer and visualizer class, to analyze the successful result files and
        create plots of the results
        :return:
        '''


        tt = traffic_type
        trace = traffic_trace

        #to indicate the email_adapter, which traffic type is analyzed
        is_synthetic = True
        if tt == "realistic":
            is_synthetic=False


        self.log.debug("Analyzing trace (%s,%s)" % (tt,trace))
        curframe = inspect.currentframe()
        calframe = inspect.getouterframes(curframe, 2)
        self.log.debug('caller name: %s' % calframe[1][3])

        # #synthetic and realistic results are process differently, so
        # #different class variables are used to store the data

        # Pktgen (re)start(s) finished, analyze results
        results_analyzer = ResultsAnalyzer(self.config,
                                            trafficType=tt,
                                            traffic_trace=trace)

        # after analyzation is done, visualize results
        results = results_analyzer.getResultsDict()
        visualizer = Visualizer(config=self.config,
                                 results=results,
                                 type=tt,
                                 traffic_trace=trace)

        #check whether user wants to store the results in the database
        if not self.no_database:
            database_handler = DatabaseHandler(config=self.config,
                                                results=results,
                                                type=tt,
                                                traffic_trace=trace)
        # send notification email -- Last boolean parameter indicates synthetic case
        if (self.config['email_adapter'] is not None) and \
            (not self.config['email_adapter'].sendResultsMail(trace, is_synthetic)):
            self.log.warn("Sending email did not succeed...SKIPPING")


    def startPktgenMeasurements(self):
        '''
        This  function is actually doing the stuff. It assembles the pktgen command
        and corresponding lua scripts, then starts the measurements
        :return:
        '''
        self.log.info("+----------------------------------------------+")
        self.log.info(str("|-    Estimated time required: %s        -|" % 
                          self.config['ETL']))
        self.log.info("+----------------------------------------------+")
        time.sleep(2)


        if self.config["trafficTypes"]:
            
            self.log.info(str("Pktgen will be started %s times" % 
                              self.config["measurement_num"]))

            #iterate through traffic types
            for trafficType in self.config["trafficTypes"]:
                #first, measure simple scenarios (if desired)
                if(trafficType == "simple"):
                    self.log.warn("SIMPLE TRACE - %s" % trafficType)

                    # configure VNF if set
                    if self.config["control_nfpa"]:
                        if not self.configureVNFRemote(self.config["vnf_function"],trafficType):
                            # configuring vnf did not succeed
                            if (self.config['email_adapter'] is not None) and \
                                (not self.config['email_adapter'].sendErrorMail()):
                                self.log.error("Sending ERROR email did not succeed...")
                            exit(-1)

                    #create config file for LUA script
                    self.rc.generateLuaConfigFile(trafficType,
                                                  self.config["packetSizes"],
                                                  None)
                    #append simple lua script to pktgen command
                    cmd = self.rc.assemblePktgenCommand()
                    cmd += " -f nfpa_simple.lua"
                    self.log.info("PKTgen command: %s" % cmd)

                    #sleep 1s for reading command
                    time.sleep(1)

                    #change dir to pktgen's main dir
                    cd_cmd = "cd " + self.config["PKTGEN_ROOT"]

                    #concatenate main command
                    main_cmd = cd_cmd + " && " + cmd
                    #here should be start the actual pktgen command!
                    #we can't use our invoke function, since we could
                    #not follow pktgen's output due to forking

                    #start pktgen in measurement_num times
                    for i in range(0, int(self.config["measurement_num"])):
                        retval = os.system(main_cmd)
                        if (retval != 0):
                            self.log.error("ERROR OCCURRED DURING STARTING PKTGEN")

                            if (self.config['email_adapter'] is not None) and \
                                    (not self.config['email_adapter'].sendErrorMail()):
                                self.log.error("Sending ERROR email did not succeed...")
                            exit(-1)


                else:
                    # configure VNF if set
                    if self.config["control_nfpa"]:
                        if not self.configureVNFRemote(self.config["vnf_function"], trafficType):
                            # configuring vnf did not succeed
                            if (self.config['email_adapter'] is not None) and \
                                (not self.config['email_adapter'].sendErrorMail()):
                                self.log.error("Sending ERROR email did not succeed...")
                            exit(-1)

                    for ps in self.config['packetSizes']:
                        #create config file for LUA script
                        self.rc.generateLuaConfigFile(trafficType,
                                                      [ps],
                                                      None)
                        #create the command first part
                        cmd = self.rc.assemblePktgenCommand()
                        #no special bidirectional traffic was not set
                        if not sbtc.checkSpecialTraffic(trafficType):
                            cmd += " -f nfpa_traffic.lua -s " + \
                                  self.config["sendPort"] + ":" + \
                                  self.config['MAIN_ROOT'] + \
                                  "/PCAP/nfpa." +\
                                  trafficType + "." + ps + "bytes.pcap"

                            #if bidDir is set, we need to set pcap file for the
                            #other port as well (add this part to the cmd)
                            if(int(self.config["biDir"]) == 1):
                                cmd +=  " -s " + self.config["recvPort"] +\
                                        ":" + self.config['MAIN_ROOT'] +\
                                        "/PCAP/nfpa." +\
                                        trafficType + "." + ps + "bytes.pcap"
                        else:
                            #special bidirectional traffic was set
                            tmp_tt = sbtc.splitTraffic(trafficType)
                            cmd += " -f nfpa_traffic.lua -s " + \
                                    self.config["sendPort"] + ":" + \
                                    self.config['MAIN_ROOT'] + \
                                    "/PCAP/nfpa." + tmp_tt[0] + "." + \
                                    ps + "bytes.pcap"
                            cmd +=  " -s " + self.config["recvPort"] + \
                                    ":" + self.config['MAIN_ROOT'] + \
                                    "/PCAP/nfpa." + tmp_tt[1] + "." + \
                                    ps + "bytes.pcap"

                        self.log.info(cmd)
                        #sleep 1s for reading command
                        time.sleep(1)


                        #change dir to pktgen's main dir
                        cd_cmd = "cd " + self.config["PKTGEN_ROOT"]
                        #concatenate main command
                        main_cmd = cd_cmd + " && " + cmd

                        # start pktgen in measurement_num times
                        for i in range(0, int(self.config["measurement_num"])):
                            #here should be start the actual pktgen command!
                            #we can't use our invoke function, since we could
                            #not follow pktgen's output due to forking
                            retval=os.system(main_cmd)
                            if(retval != 0):
                                self.log.error("ERROR OCCURRED DURING STARTING PKTGEN")

                                if (self.config['email_adapter'] is not None) and \
                                    (not self.config['email_adapter'].sendErrorMail()):
                                    self.log.error("Sending ERROR email did not succeed...")
                                exit(-1)
                    #ok, we got measurements for a given traffic trace
                    #with all the defined packetsizes

                # Start analyzing existing results, make plots and insert
                #data into the database
                self.startAnalyzing("synthetic", trafficType)

        
        if self.config["realisticTraffics"]:                
            #check realistic traffic traces
            for realistic in self.config["realisticTraffics"]:

                #create config file for LUA script
                self.rc.generateLuaConfigFile(None, 
                                              None,
                                              realistic)
                cmd = self.rc.assemblePktgenCommand()

                #no special bidirectional traffic was not set
                if not sbtc.checkSpecialTraffic(realistic):
                    cmd +=" -f nfpa_realistic.lua -s " + \
                          self.config["sendPort"] + ":" + \
                          self.config['MAIN_ROOT'] + "/PCAP/nfpa." +\
                          realistic + ".pcap" 
                
                    #if bidDir is set, we need to set pcap file for the 
                    #other port as well (add this part to the cmd)
                    if(int(self.config["biDir"]) == 1):
                        cmd += " -s " + self.config["recvPort"] + ":" + \
                               self.config['MAIN_ROOT'] + "/PCAP/nfpa." +\
                               realistic + ".pcap"
                
                #special bidirectional traffic was set
                else:
                    tmp_tt = sbtc.splitTraffic(realistic)
                    cmd += " -f nfpa_realistic.lua -s " + \
                           self.config["sendPort"] + ":" + \
                           self.config['MAIN_ROOT'] + "/PCAP/nfpa." +\
                           tmp_tt[0] + ".pcap" 
                    
                    cmd +=  " -s " + self.config["recvPort"] + \
                            ":" + self.config['MAIN_ROOT'] + \
                            "/PCAP/nfpa." + tmp_tt[1] + ".pcap"         
                    
                self.log.info(cmd)
                
                #sleep 1s for reading command
                time.sleep(1)
                
                #change dir to pktgen's main dir
                cd_cmd = "cd " + self.config["PKTGEN_ROOT"]
                #concatenate main command
                main_cmd = cd_cmd + " && " + cmd

                # start pktgen in measurement_num times
                for i in range(0, int(self.config["measurement_num"])):
                    #here should be start the actual pktgen command!
                    #we can't use our invoke function, since we could
                    #not follow pktgen's output due to forking
                    retval=os.system(main_cmd)
                    if(retval != 0):
                        self.log.error("ERROR OCCURRED DURING STARTING PKTGEN")


                        if (self.config['email_adapter'] is not None) and \
                            (not self.config['email_adapter'].sendErrorMail()):
                            self.log.error("Sending ERROR email did not succeed...")
                        exit(-1)

                # Start analyzing existing results
                self.startAnalyzing("realistic", realistic)
             

        
        #after everything is done, delete unnecessary res files
        self.deleteResFiles()

        stop = time.time()        
        start = self.config['app_start_date'] 
         
        running_time =  float(stop) - float(start)
        running_time = str(datetime.timedelta(seconds=running_time))
        self.log.info(str("Time elapsed: %s") % running_time)

        self.log.info("Log file can be found under: %s" % self.log_file_path)
        self.log.info("THANK YOU FOR USING NFPA %s" % self.config['version'])

        if(self.reset_terminal):
            self.log.info("Resetting terminal...")
            time.sleep(1)
            os.system("reset")
            #print out log automatically in this case to simulate 'no-reset' effect
            print_log_cmd="cat " + self.log_file_path
            os.system(print_log_cmd)
              
    
    def deleteResFiles(self):
        '''
        This function will delete all the temporary results files under
        pktgen's main directory
        '''
        #all files look like nfpa.[traffic_type].[packetsize]bytes.res
        #besides those, only 2 symlinks exist, which could also be deleted,
        #since each restart it is recreated. However, we do not delete them!
        del_cmd = "rm -rf " + self.config["PKTGEN_ROOT"] + "/nfpa.*.res"
        invoke.invoke(command=del_cmd,
                      logger=self.log)