Esempio n. 1
0
def main():
    host = "localhost"
    base_url = "http://%s:8080" % host
    resource_id = 1  # typically you're using resource 1 in stand alone realm
    radio = "wiphy0"
    start_id = 200
    end_id = 202
    padding_number = 10000  # the first digit of this will be deleted
    ssid = "jedway-wpa2-x2048-4-1"
    passphrase = "jedway-wpa2-x2048-4-1"

    parser = argparse.ArgumentParser(description="test creating a station")
    parser.add_argument("-m",
                        "--host",
                        type=str,
                        help="json host to connect to")
    parser.add_argument("-r",
                        "--radio",
                        type=str,
                        help="radio to create a station on")
    parser.add_argument("-a",
                        "--start_id",
                        type=int,
                        help="starting station id")
    parser.add_argument("-b", "--end_id", type=int, help="ending station id")
    parser.add_argument("-s", "--ssid", type=str, help="station ssid")
    parser.add_argument("-p", "--passwd", type=str, help="password for ssid")

    args = None
    try:
        args = parser.parse_args()
        if (args.host is not None):
            host = args.host,
            baseurl = base_url = "http://%s:8080" % host
        if (args.radio is not None):
            radio = args.radio
        if (args.start_id is not None):
            start_id = args.start_id
        if (args.end_id is not None):
            end_id = args.end_id
        if (args.ssid is not None):
            ssid = args.ssid
        if (args.passwd is not None):
            passphrase = args.passwd
    except Exception as e:
        logging.exception(e)
        usage()
        exit(2)

    # station numbers are heavily manipulated strings, often using manual padding
    # sta200 is not sta0200 nor sta00200, and we can format these numbers by adding
    # a 1000 or 10000 to the station id, and trimming the first digit off

    j_printer = pprint.PrettyPrinter(indent=2)
    json_post = ""
    json_response = ""
    found_stations = []
    lf_r = LFRequest.LFRequest(base_url + "/port/1/1/wiphy0")
    wiphy0_json = lf_r.getAsJson()
    if (wiphy0_json is None) or (wiphy0_json['interface'] is None):
        print("Unable to find radio. Are we connected?")
        exit(1)

    # If you need to inspect a radio....
    #print("# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
    #print("# radio wiphy0                                              -")
    #print("# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
    #LFUtils.debug_printer.pprint(wiphy0_json['interface']['alias'])
    #parent_radio_mac = wiphy0_json['interface']['mac']
    #print("# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Example 1                                                 -
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # This section uses URLs /cli-form/rm_vlan, /cli-form/add_sta
    # The /cli-form URIs take URL-encoded form posts
    #
    # For each of the station names, delete them if they exist. It
    # takes a few milliseconds to delete them, so after deleting them
    # you need to poll until they don't appear.
    #
    # NOTE: the ID field of the EID is ephemeral, so best stick to
    # requesting the station name. The station name can be formatted
    # with leading zeros, sta00001 is legal
    # and != {sta0001, sta001, sta01, or sta1}

    desired_stations = LFUtils.portNameSeries("sta", start_id, end_id,
                                              padding_number)
    #LFUtils.debug_printer.pprint(desired_stations)
    print("Example 1: will create stations %s" % (",".join(desired_stations)))
    for sta_name in desired_stations:
        url = base_url + "/port/1/%s/%s" % (resource_id, sta_name)
        print("Ex 1: Checking for station : " + url)
        lf_r = LFRequest.LFRequest(url)
        json_response = lf_r.getAsJson(show_error=False)
        if (json_response != None):
            found_stations.append(sta_name)

    for sta_name in found_stations:
        print("Ex 1: Deleting station %s ...." % sta_name)
        lf_r = LFRequest.LFRequest(base_url + "/cli-form/rm_vlan")
        lf_r.addPostData({
            "shelf": 1,
            "resource": resource_id,
            "port": sta_name,
            "suppress_preexec_cli": "yes",
            "suppress_preexec_method": 1
        })
        json_response = lf_r.formPost()
        sleep(0.05
              )  # best to give LANforge a few millis between rm_vlan commands

    LFUtils.waitUntilPortsDisappear(resource_id, base_url, found_stations)

    print("Ex 1: Next we create stations...")
    #68727874560 was previous flags
    for sta_name in desired_stations:
        print("Ex 1: Next we create station %s" % sta_name)
        lf_r = LFRequest.LFRequest(base_url + "/cli-form/add_sta")
        # flags are a decimal equivalent of a hexadecimal bitfield
        # you can submit as either 0x(hex) or (dec)
        # a helper page is available at http://localhost:8080/help/add_sta
        #
        # You can watch console output of the LANforge GUI client when you
        # get errors to this command, and you can also watch the websocket
        # output for a response to this command at ws://localhost:8081
        # Use wsdump ws://localhost:8081/
        #
        # modes are listed at http://<YOUR_LANFORGE>/LANforgeDocs-5.4.1/lfcli_ug.html
        # or at https://www.candelatech.com/lfcli_ug.html
        #
        # mac address field is a pattern for creation: entirely random mac addresses
        # do not take advantage of address mask matchin in Ath10k hardware, so we developed
        # this pattern to randomize a section of octets. XX: keep parent, *: randomize, and
        # chars [0-9a-f]: use this digit
        #
        # If you get errors like "X is invalid hex chara cter", this indicates a previous
        # rm_vlan call has not removed your station yet: you cannot rewrite mac addresses
        # with this call, just create new stations
        lf_r.addPostData(
            LFUtils.staNewDownStaRequest(sta_name,
                                         resource_id=resource_id,
                                         radio=radio,
                                         ssid=ssid,
                                         passphrase=passphrase))
        lf_r.formPost()
        sleep(0.1)

    LFUtils.waitUntilPortsAppear(resource_id, base_url, desired_stations)
    for sta_name in desired_stations:
        sleep(1)
        print("doing portSetDhcpDownRequest on " + sta_name)
        lf_r = LFRequest.LFRequest(base_url + "/cli-form/set_port")
        lf_r.addPostData(LFUtils.portSetDhcpDownRequest(resource_id, sta_name))
        lf_r.formPost()

    # the LANforge API separates STA creation and ethernet port settings
    # We need to revisit the stations we create and amend flags to add
    # things like DHCP or ip+gateway, admin-{up,down}

    LFUtils.waitUntilPortsAppear(resource_id, base_url, desired_stations)
    for sta_name in desired_stations:
        sleep(1)
        print("Ex 1: station up %s" % sta_name)
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/set_port")
        data = LFUtils.portDhcpUpRequest(resource_id, sta_name)
        lf_r.addPostData(data)
        lf_r.jsonPost()

    LFUtils.waitUntilPortsAppear(resource_id, base_url, desired_stations)
    # for sta_name in desired_stations:
    #     print("Ex 1: sta down %s"%sta_name)
    #     lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_port")
    #     lf_r.addPostData(LFUtils.portDownRequest(resource_id, sta_name))
    #     lf_r.jsonPost()
    #     sleep(0.05)
    print("...done with example 1\n\n")
    sleep(4)

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Example 2                                                 -
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # uses URLs /cli-json/rm_vlan, /cli-json/add_sta
    # and those accept POST in json formatted text
    desired_stations = []
    found_stations = []
    start_id = 220
    end_id = 222
    desired_stations = LFUtils.portNameSeries("sta", start_id, end_id,
                                              padding_number)

    print("Example 2: using port list to find stations")
    sleep(1)
    url = base_url + "/port/1/%s/list?fields=alias" % (resource_id)
    lf_r = LFRequest.LFRequest(url)
    json_response = lf_r.getAsJson()
    if json_response == None:
        raise Exception("no reponse to: " + url)
    port_map = LFUtils.portListToAliasMap(json_response)
    #LFUtils.debug_printer.pprint(port_map)

    for sta_name in desired_stations:
        print("Ex 2: checking for station : " + sta_name)
        if sta_name in port_map.keys():
            #print("found station : "+sta_name)
            found_stations.append(sta_name)

    for sta_name in found_stations:
        print("Ex 2: delete station %s ..." % sta_name)
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/rm_vlan")
        lf_r.addPostData({
            "shelf": 1,
            "resource": resource_id,
            "port": sta_name
        })
        lf_r.jsonPost(show_error=False)
        sleep(0.05)

    LFUtils.waitUntilPortsDisappear(resource_id, base_url, found_stations)
    for sta_name in desired_stations:
        print("Ex 2: create station %s" % sta_name)
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/add_sta")
        lf_r.addPostData(
            LFUtils.staNewDownStaRequest(sta_name,
                                         resource_id=resource_id,
                                         radio=radio,
                                         ssid=ssid,
                                         passphrase=passphrase))
        lf_r.jsonPost()
        sleep(1)

    LFUtils.waitUntilPortsAppear(resource_id, base_url, desired_stations)
    # the LANforge API separates STA creation and ethernet port settings
    # We need to revisit the stations we create and amend flags to add
    # things like DHCP or ip+gateway, admin-{up,down}
    for sta_name in desired_stations:
        print("Ex 2: set port %s" % sta_name)
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/set_port")
        data = LFUtils.portDhcpUpRequest(resource_id, sta_name)
        lf_r.addPostData(data)
        lf_r.jsonPost()
        sleep(0.05)

    print("...done with Example 2")
    sleep(1)

    print("Example 3: bring ports up and down")
    sleep(1)
    print("Ex 3: setting ports up...")
    desired_stations.insert(0, "sta0200")
    desired_stations.insert(1, "sta0201")
    desired_stations.insert(2, "sta0202")
    wait_for_these = []
    for sta_name in desired_stations:
        lf_r = LFRequest.LFRequest(base_url +
                                   "/port/1/%s/%s?fields=port,device,down" %
                                   (resource_id, sta_name))
        json_response = lf_r.getAsJson()
        if json_response['interface']['down'] is 'true':
            url = base_url + "/cli-json/set_port"
            lf_r = LFRequest.LFRequest(url)
            lf_r.addPostData(LFUtils.portDhcpUpRequest(resource_id, sta_name))
            print("setting %s up" % sta_name)
            lf_r.jsonPost()
            wait_for_these.append(sta_name)
    LFUtils.waitUntilPortsAdminUp(resource_id, base_url, wait_for_these)
    sleep(4)
    print("Ex 3: setting ports down...")
    for sta_name in desired_stations:
        lf_r = LFRequest.LFRequest(base_url +
                                   "/port/1/%s/%s?fields=port,device,down" %
                                   (resource_id, sta_name))
        json_response = lf_r.getAsJson()
        if json_response['interface']['down'] is 'false':
            url = base_url + "/cli-json/set_port"
            lf_r = LFRequest.LFRequest(url)
            lf_r.addPostData(LFUtils.portDownRequest(resource_id, sta_name))
            print("setting %s down" % sta_name)
            lf_r.jsonPost()
            wait_for_these.append(sta_name)
    LFUtils.waitUntilPortsAdminDown(resource_id, base_url, wait_for_these)
    print("...ports are down")
    sleep(4)

    print("Example 4: Modify stations to mode /a")
    sleep(1)
    for sta_name in desired_stations:
        #lf_r = LFRequest.LFRequest(base_url+"/port/1/%s/%s"%(resource_id, sta_name))
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/set_port")
        lf_r.addPostData(LFUtils.portDownRequest(resource_id, sta_name))
        lf_r.jsonPost()
    LFUtils.waitUntilPortsAdminDown(resource_id, base_url, desired_stations)

    for sta_name in desired_stations:
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/add_sta")
        lf_r.addPostData({
            "shelf": 1,
            "resource": resource_id,
            "radio": radio,
            "sta_name": sta_name,
            "mode":
            1,  # 802.11a see http://www.candelatech.com/lfcli_ug.php#add_sta
        })
        print("using add_sta to set %s mode" % sta_name)
        lf_r.jsonPost()
        sleep(0.5)

    for sta_name in desired_stations:
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/set_port")
        lf_r.addPostData(LFUtils.portUpRequest(resource_id, sta_name))
        lf_r.get()
    LFUtils.waitUntilPortsAdminUp(resource_id, base_url, desired_stations)
    print("...done")
    sleep(4)

    print("Example 5: change station encryption from wpa2 to wpa3...")
    sleep(1)
    for sta_name in desired_stations:
        #lf_r = LFRequest.LFRequest(base_url+"/port/1/%s/%s"%(resource_id, sta_name))
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/set_port")
        lf_r.addPostData(LFUtils.portDownRequest(resource_id, sta_name))
        lf_r.get()
    LFUtils.waitUntilPortsAdminDown(resource_id, base_url, desired_stations)

    for sta_name in desired_stations:
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/add_sta")
        lf_r.addPostData({
            "shelf": 1,
            "resource": resource_id,
            "radio": radio,
            "sta_name": sta_name,
            "mode": 0,  # mode AUTO
            "flags": 1099511627776,  # sets use-wpa3
            "flags_mask":
            1099511628800  # sets interest in use-wpa3, wpa2_enable (becomes zero)
        })
        print("using add_sta to set %s wpa3" % sta_name)
        lf_r.jsonPost()
        sleep(0.5)

    for sta_name in desired_stations:
        lf_r = LFRequest.LFRequest(base_url + "/cli-json/set_port")
        lf_r.addPostData(LFUtils.portUpRequest(resource_id, sta_name))
        lf_r.get()
    LFUtils.waitUntilPortsAdminUp(resource_id, base_url, desired_stations)
    print("...done")
    sleep(4)

    print("Example 7: alter TX power on %s..." % radio)
    # virtual stations do not have individual tx power states
    sleep(1)
    # see http://www.candelatech.com/lfcli_ug.php#set_wifi_radio
    lf_r = LFRequest.LFRequest(base_url + "/cli-json/set_wifi_radio")
    lf_r.addPostData({
        "shelf": 1,
        "resource": resource_id,
        "radio": radio,
        "mode": NA,
        # tx power see: man 8 iwconfig, power is in dBm, auto or off
        "txpower": "auto",
        # meta flag tells lfclient to not check port before issuing command
        "suppress_preexec_method": "true",
    })
    lf_r.jsonPost()
Esempio n. 2
0
    def run(self):
        parser = argparse.ArgumentParser(
            description="Create max stations for each radio")
        parser.add_argument(
            "--test_duration",
            type=str,
            help=
            "Full duration for the test to run. Should be specified by a number followed by a "
            "character. d for days, h for hours, m for minutes, s for seconds")
        parser.add_argument(
            "--test_end_time",
            type=str,
            help=
            "Specify a time and date to end the test. Should be formatted as "
            "year-month-date_hour:minute. Date should be specified in numbers and time "
            "should be 24 "
            "hour format. Ex: 2020-5-14_14:30")
        parser.add_argument(
            "--report_interval",
            type=str,
            help="How often a report is made. Should be specified by a "
            "number followed by a character. d for days, h for hours, "
            "m for minutes, s for seconds")
        parser.add_argument("--output_dir",
                            type=str,
                            help="Directory to output to")
        parser.add_argument(
            "--output_prefix",
            type=str,
            help=
            "Name of the file. Timestamp and .html will be appended to the end"
        )
        parser.add_argument("--email",
                            type=str,
                            help="Email address of recipient")

        args = None
        try:
            args = parser.parse_args()
            if args.test_duration is not None:
                pattern = re.compile("^(\d+)([dhms]$)")
                td = pattern.match(args.test_duration)
                if td is not None:
                    dur_time = int(td.group(1))
                    dur_measure = str(td.group(2))
                    now = datetime.datetime.now()
                    if dur_measure == "d":
                        duration_time = datetime.timedelta(days=dur_time)
                    elif dur_measure == "h":
                        duration_time = datetime.timedelta(hours=dur_time)
                    elif dur_measure == "m":
                        duration_time = datetime.timedelta(minutes=dur_time)
                    else:
                        duration_time = datetime.timedelta(seconds=dur_time)
                else:
                    parser.print_help()
                    parser.exit()

            elif args.test_end_time is not None:
                now = datetime.datetime.now()
                try:
                    end_time = datetime.datetime.strptime(
                        args.test_end_time, "%Y-%m-%d_%H:%M")
                    if end_time < now:
                        parser.print_help()
                        raise ValueError
                    else:
                        cur_time = datetime.datetime.now()
                        duration_time = end_time - cur_time

                except ValueError as exception:
                    print(exception)
                    parser.print_help()
                    parser.exit()

            else:
                parser.print_help()
                parser.exit()

            if args.report_interval is not None:
                pattern = re.compile("^(\d+)([dhms])$")
                ri = pattern.match(args.report_interval)
                if ri is not None:
                    int_time = int(ri.group(1))
                    int_measure = str(ri.group(2))

                    if int_measure == "d":
                        interval_time = datetime.timedelta(days=int_time)
                    elif int_measure == "h":
                        interval_time = datetime.timedelta(hours=int_time)
                    elif int_measure == "m":
                        interval_time = datetime.timedelta(minutes=int_time)
                    else:
                        interval_time = datetime.timedelta(seconds=int_time)
                else:
                    parser.print_help()
                    parser.exit()
            else:
                parser.print_help()
                parser.exit()

            if args.output_dir is not None:
                if not args.output_dir.endswith('/'):
                    output_dir = args.output_dir + '/'
                else:
                    output_dir = args.output_dir
            else:
                parser.print_help()
                parser.exit()

            if args.output_prefix is not None:
                output_prefix = args.output_prefix
            else:
                parser.print_help()
                parser.exit()
            if args.email is not None:
                recipient = args.email
            else:
                parser.print_help()
                parser.exit()

        except Exception as e:
            parser.print_help()
            exit(2)

        super().check_connect()

        stations = []
        radios = {
            "wiphy0": 200,  # max 200
            "wiphy1": 200,  # max 200
            "wiphy2": 64,  # max 64
            "wiphy3": 200
        }  # max 200
        # radioName:numStations
        radio_ssid_map = {
            "wiphy0": "jedway-wpa2-x2048-4-1",
            "wiphy1": "jedway-wpa2-x2048-5-3",
            "wiphy2": "jedway-wpa2-x2048-5-1",
            "wiphy3": "jedway-wpa2-x2048-4-4"
        }

        ssid_passphrase_map = {
            "jedway-wpa2-x2048-4-1": "jedway-wpa2-x2048-4-1",
            "jedway-wpa2-x2048-5-3": "jedway-wpa2-x2048-5-3",
            "jedway-wpa2-x2048-5-1": "jedway-wpa2-x2048-5-1",
            "jedway-wpa2-x2048-4-4": "jedway-wpa2-x2048-4-4"
        }

        padding_num = 1000  # uses all but the first number to create names for stations

        # clean up old stations
        self.cleanup()

        # create new stations
        print("Creating Stations")

        req_url = "cli-json/add_sta"
        for radio, numStations in radios.items():
            for i in range(0, numStations):
                sta_name = "sta" + radio[-1:] + str(padding_num + i)[1:]
                stations.append(sta_name)
                data = {
                    "shelf": 1,
                    "resource": 1,
                    "radio": radio,
                    "sta_name": sta_name,
                    "ssid": radio_ssid_map[radio],
                    "key": ssid_passphrase_map[radio_ssid_map[radio]],
                    "mode": 1,
                    "mac": "xx:xx:xx:xx:*:xx",
                    "flags": 0x400
                }
                # print("Creating station {}".format(sta_name))
                super().json_post(req_url, data)

                time.sleep(0.5)

        # LFUtils.portDhcpUpRequest(1, sta_name)

        time.sleep(10)

        # check eth1 for ip
        eth1_ip = super().json_get("port/1/1/eth1")
        if eth1_ip['interface']['ip'] == "0.0.0.0":
            print("Switching eth1 to dhcp")
            LFUtils.portDownRequest(1, "eth1")
            time.sleep(1)
            req_url = "cli-json/set_port"
            data = {
                "shelf": 1,
                "resource": 1,
                "port": "eth1",
                "current_flags": 0x80000000,
                "interest": 0x4002
            }

            super().json_post(req_url, data)
            # LFUtils.portDhcpUpRequest(1,"eth1")
            time.sleep(5)
            LFUtils.portUpRequest(1, "eth1")

        time.sleep(10)

        # create cross connects
        print("Creating cross connects")
        for sta_name in stations:
            cmd = ("./lf_firemod.pl --action create_cx --cx_name " + sta_name +
                   " --use_ports eth1," + sta_name +
                   " --use_speeds  2600,2600 --endp_type udp > sst.log")
            LFUtils.execWrap(cmd)

        # set stations to dchp up
        print("Turning on DHCP for stations")
        for sta_name in stations:
            # print("Setting {} flags".format(sta_name))
            req_url = "cli-json/set_port"
            data = {
                "shelf": 1,
                "resource": 1,
                "port": sta_name,
                "current_flags": 0x80000000,
                "interest": 0x4002
            }

            super().json_post(req_url, data)
        # LFUtils.portDhcpUpRequest(1,sta_name)

        time.sleep(15)

        # start traffic through cxs
        print("Starting CX Traffic")
        for name in stations:
            cmd = (
                "./lf_firemod.pl --mgr localhost --quiet 0 --action do_cmd --cmd \"set_cx_state default_tm "
                + name + " RUNNING\" >> sst.log")
            LFUtils.execWrap(cmd)

        # create weblog for monitoring stations
        cur_time = datetime.datetime.now().strftime("%Y-%m-%d_%H%M")
        web_log = output_dir + output_prefix + "{}.html".format(cur_time)

        try:
            web_log_file = open(web_log, "w")

        except IOError as err:
            print(err)
            print(
                "Please ensure correct permissions have been assigned in target directory"
            )
            sys.exit()

        top = """<html>
        <head>
        <title>Test report</title>
        <style>
        body, td, p, div, span { font-size: 8pt; }
        h1, h2, h3 { text-align: center; font-family: "Century Gothic",Arial,Helvetica,sans;}
        </style>
        </head>
        <body>
        <h1>Long test on %s</h1>
        <p2>Key</p2>
        <p1 style="background-color:rgb(0,255,0);">All stations associated and with ip</p1>
        <p1 style="background-color:rgb(255,200,0);">All stations associated and at least one without ip</p1>
        <p1 style="background-color:rgb(255,150,150);">No stations associated and without ip</p1>
        <table>
        """ % datetime.date.today()

        web_log_file.write(top)
        web_log_file.close()

        web_log_file = open(web_log, "a")
        web_log_file.write("<tr>\n")

        for name in radios:
            web_log_file.write("<th>{}</th>\n".format(name))

        web_log_file.write("</tr>\n")

        cur_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
        subject = "Station Test Begin Report Notification"
        body = "Report begun at {}\n See {}".format(cur_time, web_log)
        email = emailHelper.writeEmail(body)
        emailHelper.sendEmail(email, self.sender, recipient, subject)

        print("Logging Info to {}".format(web_log))

        cur_time = datetime.datetime.now()
        end_time = cur_time + duration_time

        while cur_time <= end_time:
            web_log_file.write("<tr>\n")
            for radio, numStations in radios.items():
                without_ip = 0
                dissociated = 0
                good = 0

                for i in range(0, numStations):
                    sta_name = "sta" + radio[-1:] + str(padding_num + i)[1:]
                    sta_status = super().json_get("port/1/1/" + sta_name)
                    # print(sta_name)
                    if sta_status['interface']['ip'] == "0.0.0.0":
                        without_ip += 1
                        if sta_status['interface']['ap'] is None:
                            dissociated += 1
                    else:
                        good += 1

                if without_ip and not dissociated:
                    web_log_file.write(
                        "<td style=\"background-color:rgb(255,200,0);\">{}/{}</td>\n"
                        .format(good, numStations))  # without IP assigned
                elif dissociated:
                    web_log_file.write(
                        "<td style=\"background-color:rgb(255,150,150);\">{}/{}</td>\n"
                        .format(good, numStations))  # dissociated from AP
                else:
                    web_log_file.write(
                        "<td style=\"background-color:rgb(0,255,0);\">{}/{}</td>\n"
                        .format(good, numStations))  # with IP and associated

            web_log_file.write("<td>{}</td>\n".format(
                datetime.datetime.now().strftime("%Y-%m-%d %H:%M")))
            web_log_file.write("</tr>\n")

            cur_time = datetime.datetime.now()
            int_time = cur_time + interval_time
            while cur_time <= int_time:
                # print(cur_time, int_time)
                time.sleep(1)
                cur_time = datetime.datetime.now()
            # sleep(1)
            cur_time = datetime.datetime.now()

        web_log_file.write("</table></body></html>\n")
        web_log_file.close()

        cur_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
        subject = "Station Test End Report Notification"
        body = "Report finished at {} see {}".format(cur_time, web_log)
        email = emailHelper.writeEmail(body)
        emailHelper.sendEmail(email, self.sender, recipient, subject)

        print("Stopping CX Traffic")
        for sta_name in stations:
            cmd = (
                "./lf_firemod.pl --mgr localhost --quiet 0 --action do_cmd --cmd \"set_cx_state default_tm "
                + sta_name + " STOPPED\" >> sst.log")
            LFUtils.execWrap(cmd)

        time.sleep(10)

        # remove all created stations and cross connects

        print("Cleaning Up...")
        self.cleanup()